├── .artifactignore ├── .github ├── codeql │ └── codeql-config.yml ├── pull_request_template.md └── workflows │ ├── codeql-analysis.yml │ └── semgrep.yml ├── .gitignore ├── LICENSE ├── README.md ├── azure-pipelines.yml ├── sonic-chassisd ├── pytest.ini ├── scripts │ ├── chassis_db_init │ └── chassisd ├── setup.cfg ├── setup.py └── tests │ ├── __init__.py │ ├── mock_module_base.py │ ├── mock_platform.py │ ├── mock_swsscommon.py │ ├── mocked_libs │ └── sonic_platform │ │ ├── __init__.py │ │ ├── chassis.py │ │ └── platform.py │ ├── test_chassis_db_init.py │ ├── test_chassisd.py │ └── test_dpu_chassisd.py ├── sonic-ledd ├── pytest.ini ├── scripts │ └── ledd ├── setup.cfg ├── setup.py └── tests │ ├── __init__.py │ ├── mock_swsscommon.py │ └── test_ledd.py ├── sonic-pcied ├── pytest.ini ├── scripts │ └── pcied ├── setup.cfg ├── setup.py └── tests │ ├── __init__.py │ ├── mock_platform.py │ ├── mocked_libs │ ├── sonic_platform │ │ ├── __init__.py │ │ └── pcie.py │ ├── sonic_platform_base │ │ ├── __init__.py │ │ └── sonic_pcie │ │ │ ├── __init__.py │ │ │ ├── pcie_base.py │ │ │ └── pcie_common.py │ └── swsscommon │ │ ├── __init__.py │ │ └── swsscommon.py │ ├── test_DaemonPcied.py │ └── test_pcied.py ├── sonic-psud ├── pytest.ini ├── scripts │ └── psud ├── setup.cfg ├── setup.py └── tests │ ├── __init__.py │ ├── mock_platform.py │ ├── mocked_libs │ ├── sonic_platform │ │ ├── __init__.py │ │ └── psu.py │ └── swsscommon │ │ ├── __init__.py │ │ └── swsscommon.py │ ├── test_DaemonPsud.py │ ├── test_PsuChassisInfo.py │ ├── test_PsuStatus.py │ └── test_psud.py ├── sonic-sensormond ├── pytest.ini ├── scripts │ └── sensormond ├── setup.cfg ├── setup.py └── tests │ ├── __init__.py │ ├── mock_platform.py │ ├── mock_swsscommon.py │ ├── mocked_libs │ └── sonic_platform │ │ ├── __init__.py │ │ ├── chassis.py │ │ └── platform.py │ ├── sensors.yaml │ └── test_sensormond.py ├── sonic-stormond ├── pytest.ini ├── scripts │ └── stormond ├── setup.cfg ├── setup.py └── tests │ ├── __init__.py │ ├── mock_swsscommon.py │ ├── mocked_libs │ ├── sonic_platform │ │ ├── __init__.py │ │ └── pcie.py │ ├── sonic_platform_base │ │ ├── __init__.py │ │ └── sonic_storage │ │ │ ├── __init__.py │ │ │ ├── storage_base.py │ │ │ └── storage_devices.py │ └── swsscommon │ │ ├── __init__.py │ │ └── swsscommon.py │ └── test_DaemonStorage.py ├── sonic-syseepromd ├── pytest.ini ├── scripts │ └── syseepromd ├── setup.cfg ├── setup.py └── tests │ ├── __init__.py │ ├── mocked_libs │ ├── sonic_platform │ │ ├── __init__.py │ │ ├── chassis.py │ │ └── platform.py │ └── swsscommon │ │ ├── __init__.py │ │ └── swsscommon.py │ └── test_syseepromd.py ├── sonic-thermalctld ├── pytest.ini ├── scripts │ └── thermalctld ├── setup.cfg ├── setup.py └── tests │ ├── __init__.py │ ├── mock_platform.py │ ├── mock_swsscommon.py │ ├── mocked_libs │ ├── sonic_platform │ │ ├── __init__.py │ │ ├── chassis.py │ │ └── platform.py │ └── swsscommon │ │ ├── __init__.py │ │ └── swsscommon.py │ └── test_thermalctld.py ├── sonic-xcvrd ├── pytest.ini ├── setup.cfg ├── setup.py ├── tests │ ├── __init__.py │ ├── media_settings.json │ ├── media_settings_extended_format.json │ ├── mock_platform.py │ ├── mock_swsscommon.py │ ├── optics_si_settings.json │ ├── t0-sample-port-config.ini │ └── test_xcvrd.py └── xcvrd │ ├── __init__.py │ ├── cmis │ ├── __init__.py │ └── cmis_manager_task.py │ ├── dom │ ├── __init__.py │ ├── dom_mgr.py │ └── utilities │ │ ├── __init__.py │ │ ├── db │ │ ├── __init__.py │ │ └── utils.py │ │ ├── dom_sensor │ │ ├── __init__.py │ │ ├── db_utils.py │ │ └── utils.py │ │ ├── status │ │ ├── __init__.py │ │ ├── db_utils.py │ │ └── utils.py │ │ └── vdm │ │ ├── __init__.py │ │ ├── db_utils.py │ │ └── utils.py │ ├── sff_mgr.py │ ├── xcvrd.py │ └── xcvrd_utilities │ ├── __init__.py │ ├── common.py │ ├── media_settings_parser.py │ ├── optics_si_parser.py │ ├── port_event_helper.py │ ├── sfp_status_helper.py │ ├── utils.py │ └── xcvr_table_helper.py └── sonic-ycabled ├── proto └── proto_out │ └── linkmgr_grpc_driver.proto ├── proto_out └── __init__.py ├── pytest.ini ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── mock_swsscommon.py ├── test_y_cable_helper.py └── test_ycable.py └── ycable ├── __init__.py ├── ycable.py └── ycable_utilities ├── __init__.py ├── y_cable_helper.py └── y_cable_table_helper.py /.artifactignore: -------------------------------------------------------------------------------- 1 | **/* 2 | !*/dist/*.whl 3 | -------------------------------------------------------------------------------- /.github/codeql/codeql-config.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL config" 2 | queries: 3 | - uses: security-and-quality 4 | - uses: security-extended 5 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | #### Description 4 | 7 | 8 | #### Motivation and Context 9 | 14 | 15 | #### How Has This Been Tested? 16 | 21 | 22 | #### Additional Information (Optional) 23 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For more infomation, please visit: https://github.com/github/codeql-action 2 | 3 | name: "CodeQL" 4 | 5 | on: 6 | push: 7 | branches: 8 | - 'master' 9 | - '202[0-9][0-9][0-9]' 10 | pull_request_target: 11 | branches: 12 | - 'master' 13 | - '202[0-9][0-9][0-9]' 14 | 15 | jobs: 16 | analyze: 17 | name: Analyze 18 | runs-on: ubuntu-latest 19 | permissions: 20 | actions: read 21 | contents: read 22 | security-events: write 23 | 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | language: [ 'python' ] 28 | 29 | steps: 30 | - name: Checkout repository 31 | uses: actions/checkout@v3 32 | 33 | # Initializes the CodeQL tools for scanning. 34 | - name: Initialize CodeQL 35 | uses: github/codeql-action/init@v2 36 | with: 37 | config-file: ./.github/codeql/codeql-config.yml 38 | languages: ${{ matrix.language }} 39 | 40 | - name: Perform CodeQL Analysis 41 | uses: github/codeql-action/analyze@v2 42 | with: 43 | category: "/language:${{matrix.language}}" 44 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | name: Semgrep 2 | 3 | on: 4 | pull_request: {} 5 | push: 6 | branches: 7 | - master 8 | - '201[7-9][0-1][0-9]' 9 | - '202[0-9][0-1][0-9]' 10 | 11 | jobs: 12 | semgrep: 13 | if: github.repository_owner == 'sonic-net' 14 | name: Semgrep 15 | runs-on: ubuntu-latest 16 | container: 17 | image: returntocorp/semgrep 18 | steps: 19 | - uses: actions/checkout@v3 20 | - run: semgrep ci 21 | env: 22 | SEMGREP_RULES: p/default 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | */*.egg-info/ 3 | */*.tar.gz 4 | */.cache/ 5 | */.eggs/ 6 | */__pycache__/ 7 | */build/ 8 | */deb_dist/ 9 | */dist/ 10 | 11 | # Compiled code which doesn't end in '.pyc' 12 | sonic-thermalctld/scripts/thermalctldc 13 | sonic-chassisd/scripts/chassisdc 14 | sonic-psud/scripts/psudc 15 | 16 | # Unit test / coverage reports 17 | coverage.xml 18 | .coverage 19 | htmlcov/ 20 | test-results.xml 21 | leddc 22 | pciedc 23 | syseepromdc 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 Microsoft, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Total alerts](https://img.shields.io/lgtm/alerts/g/Azure/sonic-platform-daemons.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/Azure/sonic-platform-daemons/alerts/) 2 | [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/Azure/sonic-platform-daemons.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/Azure/sonic-platform-daemons/context:python) 3 | 4 | # SONiC: Software for Open Networking in the Cloud 5 | 6 | ## sonic-platform-daemons 7 | 8 | Daemons for controlling platform-specific functionality in SONiC 9 | 10 | 11 | # Contribution guide 12 | 13 | All contributors must sign a contribution license agreement (CLA) before contributions can be accepted. This process is now automated via a GitHub bot when submitting new pull request. If the contributor has not yet signed a CLA, the bot will create a comment on the pull request containing a link to electronically sign the CLA. 14 | 15 | ### GitHub Workflow 16 | 17 | We're following basic GitHub Flow. If you have no idea what we're talking about, check out [GitHub's official guide](https://guides.github.com/introduction/flow/). Note that merge is only performed by the repository maintainer. 18 | 19 | Guide for performing commits: 20 | 21 | * Isolate each commit to one component/bugfix/issue/feature 22 | * Use a standard commit message format: 23 | 24 | > [component/folder touched]: Description intent of your changes 25 | > 26 | > [List of changes] 27 | > 28 | > Signed-off-by: Your Name your@email.com 29 | 30 | For example: 31 | 32 | > swss-common: Stabilize the ConsumerTable 33 | > 34 | > * Fixing autoreconf 35 | > * Fixing unit-tests by adding checkers and initialize the DB before start 36 | > * Adding the ability to select from multiple channels 37 | > * Health-Monitor - The idea of the patch is that if something went wrong with the notification channel, 38 | > we will have the option to know about it (Query the LLEN table length). 39 | > 40 | > Signed-off-by: John Doe user@dev.null 41 | 42 | 43 | * Each developer should fork this repository and [add the team as a Contributor](https://help.github.com/articles/adding-collaborators-to-a-personal-repository) 44 | * Push your changes to your private fork and do "pull-request" to this repository 45 | * Use a pull request to do code review 46 | * Use issues to keep track of what is going on 47 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Start with a minimal pipeline that you can customize to build and deploy your code. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | 6 | trigger: 7 | branches: 8 | include: 9 | - '*' 10 | 11 | variables: 12 | DIFF_COVER_CHECK_THRESHOLD: 80 13 | DIFF_COVER_ENABLE: 'true' 14 | DIFF_COVER_WORKING_DIRECTORY: $(System.DefaultWorkingDirectory) 15 | 16 | pr: 17 | branches: 18 | include: 19 | - '*' 20 | 21 | pool: sonictest 22 | 23 | resources: 24 | containers: 25 | - container: sonic-slave-bookworm 26 | image: sonicdev-microsoft.azurecr.io:443/sonic-slave-bookworm:latest 27 | 28 | parameters: 29 | - name: project_list 30 | type: object 31 | default: 32 | - name: chassisd 33 | root_dir: sonic-chassisd 34 | python3: true 35 | - name: ledd 36 | root_dir: sonic-ledd 37 | python3: true 38 | - name: pcied 39 | root_dir: sonic-pcied 40 | python3: true 41 | - name: psud 42 | root_dir: sonic-psud 43 | python3: true 44 | - name: syseepromd 45 | root_dir: sonic-syseepromd 46 | python3: true 47 | - name: thermalctld 48 | root_dir: sonic-thermalctld 49 | python3: true 50 | - name: xcvrd 51 | root_dir: sonic-xcvrd 52 | python3: true 53 | - name: ycabled 54 | root_dir: sonic-ycabled 55 | python3: true 56 | - name: sensormond 57 | root_dir: sonic-sensormond 58 | python3: true 59 | - name: stormond 60 | root_dir: sonic-stormond 61 | python3: true 62 | - name: artifactBranch 63 | type: string 64 | default: 'refs/heads/master' 65 | 66 | jobs: 67 | - job: build_test 68 | container: sonic-slave-bookworm 69 | variables: 70 | sourceBranch: "$(Build.SourceBranch)" 71 | steps: 72 | - checkout: self 73 | clean: true 74 | - script: | 75 | if [[ $(Build.Reason) == "Manual" ]];then 76 | echo "##vso[task.setvariable variable=sourceBranch]${{ parameters.artifactBranch }}" 77 | fi 78 | if [[ $(Build.Reason) == "PullRequest" ]];then 79 | echo "##vso[task.setvariable variable=sourceBranch]refs/heads/$(System.PullRequest.TargetBranch)" 80 | fi 81 | displayName: Setup artifacts download branch 82 | - task: DownloadPipelineArtifact@2 83 | inputs: 84 | source: 'specific' 85 | project: 'build' 86 | pipeline: 142 87 | artifact: 'sonic-buildimage.vs' 88 | runVersion: 'latestFromBranch' 89 | runBranch: "$(sourceBranch)" 90 | displayName: "Download artifacts from latest sonic-buildimage build" 91 | 92 | - script: | 93 | set -xe 94 | sudo apt-get -y purge libnl-3-dev libnl-route-3-dev || true 95 | sudo dpkg -i libnl-3-200_*.deb 96 | sudo dpkg -i libnl-genl-3-200_*.deb 97 | sudo dpkg -i libnl-route-3-200_*.deb 98 | sudo dpkg -i libnl-nf-3-200_*.deb 99 | sudo dpkg -i libyang_1.0.73_amd64.deb 100 | sudo dpkg -i libswsscommon_1.0.0_amd64.deb 101 | sudo dpkg -i python3-swsscommon_1.0.0_amd64.deb 102 | workingDirectory: $(Pipeline.Workspace)/target/debs/bookworm/ 103 | displayName: 'Install Debian dependencies' 104 | 105 | - script: | 106 | set -xe 107 | sudo pip3 install swsssdk-2.0.1-py3-none-any.whl 108 | sudo pip3 install sonic_py_common-1.0-py3-none-any.whl 109 | sudo pip3 install sonic_yang_mgmt-1.0-py3-none-any.whl 110 | sudo pip3 install sonic_yang_models-1.0-py3-none-any.whl 111 | sudo pip3 install sonic_config_engine-1.0-py3-none-any.whl 112 | sudo pip3 install sonic_platform_common-1.0-py3-none-any.whl 113 | workingDirectory: $(Pipeline.Workspace)/target/python-wheels/bookworm/ 114 | displayName: 'Install Python dependencies' 115 | 116 | - script: | 117 | set -ex 118 | # Install .NET CORE 119 | curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - 120 | sudo apt-add-repository https://packages.microsoft.com/debian/12/prod 121 | sudo apt-get update 122 | sudo apt-get install -y dotnet-sdk-8.0 123 | displayName: "Install .NET CORE" 124 | 125 | - ${{ each project in parameters.project_list }}: 126 | # Python 3 127 | - ${{ if eq(project.python3, true) }}: 128 | - script: | 129 | set -xe 130 | # Check for missing __init__.py files for sonic-xcvrd 131 | if [ "${{ project.name }}" == "xcvrd" ]; then 132 | XCVRD_WORK_DIR=$(System.DefaultWorkingDirectory)/${{ project.root_dir }}/xcvrd 133 | missing_init_files=$(find $XCVRD_WORK_DIR -type d ! -exec test -e "{}/__init__.py" \; -print) 134 | if [ -n "$missing_init_files" ]; then 135 | echo "Error: The following directories are missing __init__.py files:" 136 | echo "$missing_init_files" 137 | exit 1 138 | fi 139 | fi 140 | pip3 install ".[testing]" 141 | pip3 uninstall --yes sonic-platform-daemons 142 | pytest 143 | workingDirectory: ${{ project.root_dir }} 144 | displayName: '${{ project.name }}(Py3) Test' 145 | 146 | - task: PublishTestResults@2 147 | inputs: 148 | testResultsFiles: '$(System.DefaultWorkingDirectory)/${{ project.root_dir }}/test-results.xml' 149 | testRunTitle: ${{ project.name }} (Python 3) 150 | failTaskOnFailedTests: true 151 | condition: succeededOrFailed() 152 | displayName: '${{ project.name }}(Py3) Publish test results' 153 | 154 | - script: | 155 | python3 -m build -n 156 | workingDirectory: ${{ project.root_dir }} 157 | displayName: '${{ project.name }}(Py3) Build' 158 | 159 | - publish: '$(System.DefaultWorkingDirectory)/${{ project.root_dir }}/dist/' 160 | artifact: ${{ project.root_dir }} 161 | displayName: "${{ project.name }}(Py3) Publish wheels" 162 | - task: reportgenerator@4 163 | inputs: 164 | reports: '$(Build.SourcesDirectory)/**/coverage.xml' 165 | targetdir: '$(Build.SourcesDirectory)/' 166 | - task: PublishCodeCoverageResults@1 167 | inputs: 168 | codeCoverageTool: 'Cobertura' 169 | summaryFileLocation: '$(Build.SourcesDirectory)/Cobertura.xml' 170 | pathToSources: $(System.DefaultWorkingDirectory)/ 171 | -------------------------------------------------------------------------------- /sonic-chassisd/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml --junitxml=test-results.xml -v 3 | -------------------------------------------------------------------------------- /sonic-chassisd/scripts/chassis_db_init: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | chassis_db_init 5 | Chassis information update tool for SONiC 6 | This tool runs one time at the launch of the platform monitor in order to populate STATE_DB with chassis information such as model, serial number, and revision. 7 | """ 8 | 9 | try: 10 | import os 11 | import sys 12 | 13 | from sonic_py_common import daemon_base, logger 14 | 15 | # If unit testing is occurring, mock swsscommon and module_base 16 | if os.getenv("CHASSIS_DB_INIT_UNIT_TESTING") == "1": 17 | from tests import mock_swsscommon as swsscommon 18 | else: 19 | from swsscommon import swsscommon 20 | except ImportError as e: 21 | raise ImportError(str(e) + " - required module not found") 22 | 23 | # 24 | # Constants 25 | # 26 | 27 | SYSLOG_IDENTIFIER = "chassis_db_init" 28 | 29 | CHASSIS_INFO_TABLE = 'CHASSIS_INFO' 30 | CHASSIS_INFO_KEY_TEMPLATE = 'chassis {}' 31 | CHASSIS_INFO_CARD_NUM_FIELD = 'module_num' 32 | CHASSIS_INFO_SERIAL_FIELD = 'serial' 33 | CHASSIS_INFO_MODEL_FIELD = 'model' 34 | CHASSIS_INFO_REV_FIELD = 'revision' 35 | 36 | CHASSIS_LOAD_ERROR = 1 37 | 38 | NOT_AVAILABLE = 'N/A' 39 | 40 | # 41 | # Helper functions ============================================================= 42 | # 43 | 44 | # try get information from platform API and return a default value if caught NotImplementedError 45 | 46 | 47 | def try_get(callback, *args, **kwargs): 48 | """ 49 | Handy function to invoke the callback and catch NotImplementedError 50 | :param callback: Callback to be invoked 51 | :param args: Arguments to be passed to callback 52 | :param kwargs: Default return value if exception occur 53 | :return: Default return value if exception occur else return value of the callback 54 | """ 55 | default = kwargs.get('default', NOT_AVAILABLE) 56 | try: 57 | ret = callback(*args) 58 | if ret is None: 59 | ret = default 60 | except NotImplementedError: 61 | ret = default 62 | 63 | return ret 64 | 65 | # 66 | # Functions 67 | # 68 | 69 | def provision_db(platform_chassis, log): 70 | # Init state db connection 71 | state_db = daemon_base.db_connect("STATE_DB") 72 | chassis_table = swsscommon.Table(state_db, CHASSIS_INFO_TABLE) 73 | 74 | # Populate DB with chassis hardware info 75 | fvs = swsscommon.FieldValuePairs([ 76 | (CHASSIS_INFO_SERIAL_FIELD, try_get(platform_chassis.get_serial)), 77 | (CHASSIS_INFO_MODEL_FIELD, try_get(platform_chassis.get_model)), 78 | (CHASSIS_INFO_REV_FIELD, try_get(platform_chassis.get_revision)) 79 | ]) 80 | chassis_table.set(CHASSIS_INFO_KEY_TEMPLATE.format(1), fvs) 81 | log.log_info("STATE_DB provisioned with chassis info.") 82 | 83 | return chassis_table 84 | 85 | 86 | # 87 | # Main 88 | # 89 | 90 | def main(): 91 | log = logger.Logger(SYSLOG_IDENTIFIER) 92 | log.log_info("Provisioning Database with Chassis Info...") 93 | 94 | # Load platform api class 95 | try: 96 | import sonic_platform.platform 97 | platform_chassis = sonic_platform.platform.Platform().get_chassis() 98 | except Exception as e: 99 | log.log_error("Failed to load chassis due to {}".format(repr(e))) 100 | sys.exit(CHASSIS_LOAD_ERROR) 101 | 102 | provision_db(platform_chassis, log) 103 | 104 | if __name__ == '__main__': 105 | main() 106 | -------------------------------------------------------------------------------- /sonic-chassisd/setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | -------------------------------------------------------------------------------- /sonic-chassisd/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='sonic-chassisd', 5 | version='1.0', 6 | description='Chassis daemon for SONiC', 7 | license='Apache 2.0', 8 | author='SONiC Team', 9 | author_email='linuxnetdev@microsoft.com', 10 | url='https://github.com/Azure/sonic-platform-daemons', 11 | maintainer='Manju Prabhu', 12 | maintainer_email='manjunath.prabhu@nokia.com', 13 | packages=[ 14 | 'tests' 15 | ], 16 | scripts=[ 17 | 'scripts/chassisd', 18 | 'scripts/chassis_db_init' 19 | ], 20 | setup_requires=[ 21 | 'pytest-runner', 22 | 'wheel' 23 | ], 24 | tests_require=[ 25 | 'pytest', 26 | 'mock>=2.0.0', 27 | 'pytest-cov' 28 | ], 29 | classifiers=[ 30 | 'Development Status :: 4 - Beta', 31 | 'Environment :: No Input/Output (Daemon)', 32 | 'Intended Audience :: Developers', 33 | 'Intended Audience :: Information Technology', 34 | 'Intended Audience :: System Administrators', 35 | 'License :: OSI Approved :: Apache Software License', 36 | 'Natural Language :: English', 37 | 'Operating System :: POSIX :: Linux', 38 | 'Programming Language :: Python :: 2.7', 39 | 'Topic :: System :: Hardware', 40 | ], 41 | keywords='sonic SONiC chassis Chassis daemon chassisd', 42 | test_suite='setup.get_test_suite' 43 | ) 44 | -------------------------------------------------------------------------------- /sonic-chassisd/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-chassisd/tests/__init__.py -------------------------------------------------------------------------------- /sonic-chassisd/tests/mock_module_base.py: -------------------------------------------------------------------------------- 1 | class ModuleBase(): 2 | # Invalid slot for modular chassis 3 | MODULE_INVALID_SLOT = -1 4 | 5 | # Possible card types for modular chassis 6 | MODULE_TYPE_SUPERVISOR = "SUPERVISOR" 7 | MODULE_TYPE_LINE = "LINE-CARD" 8 | MODULE_TYPE_FABRIC = "FABRIC-CARD" 9 | MODULE_TYPE_DPU = "DPU" 10 | 11 | # Possible card status for modular chassis 12 | # Module state is Empty if no module is inserted in the slot 13 | MODULE_STATUS_EMPTY = "Empty" 14 | # Module state if Offline if powered down. This is also the admin-down state. 15 | MODULE_STATUS_OFFLINE = "Offline" 16 | # Module state is Present when it is powered up, but not fully functional. 17 | MODULE_STATUS_PRESENT = "Present" 18 | # Module state is Present when it is powered up, but entered a fault state. 19 | # Module is not able to go Online. 20 | MODULE_STATUS_FAULT = "Fault" 21 | # Module state is Online when fully operational 22 | MODULE_STATUS_ONLINE = "Online" 23 | -------------------------------------------------------------------------------- /sonic-chassisd/tests/mock_platform.py: -------------------------------------------------------------------------------- 1 | class MockDevice: 2 | def __init__(self): 3 | self.name = None 4 | self.presence = True 5 | self.model = 'Module Model' 6 | self.serial = 'Module Serial' 7 | self.replaceable = True 8 | 9 | def get_name(self): 10 | return self.name 11 | 12 | def get_presence(self): 13 | return self.presence 14 | 15 | def get_model(self): 16 | return self.model 17 | 18 | def get_serial(self): 19 | return self.serial 20 | 21 | def is_replaceable(self): 22 | return self.replaceable 23 | 24 | class MockModule(MockDevice): 25 | def __init__(self, module_index, module_name, module_desc, module_type, module_slot, 26 | module_serial, asic_list=[]): 27 | super(MockModule, self).__init__() 28 | self.module_index = module_index 29 | self.module_name = module_name 30 | self.module_desc = module_desc 31 | self.module_type = module_type 32 | self.hw_slot = module_slot 33 | self.module_status = '' 34 | self.admin_state = 1 35 | self.supervisor_slot = 16 36 | self.midplane_access = False 37 | self.asic_list = asic_list 38 | self.module_serial = module_serial 39 | 40 | def get_name(self): 41 | return self.module_name 42 | 43 | def get_description(self): 44 | return self.module_desc 45 | 46 | def get_type(self): 47 | return self.module_type 48 | 49 | def get_slot(self): 50 | return self.hw_slot 51 | 52 | def get_oper_status(self): 53 | return self.module_status 54 | 55 | def set_oper_status(self, status): 56 | self.module_status = status 57 | 58 | def set_admin_state(self, up): 59 | self.admin_state = up 60 | 61 | def get_admin_state(self): 62 | return self.admin_state 63 | 64 | def get_midplane_ip(self): 65 | if "DPU" in self.get_name(): 66 | self.midplane_ip = '169.254.200.0' 67 | return self.midplane_ip 68 | 69 | def set_midplane_ip(self): 70 | if self.supervisor_slot == self.get_slot(): 71 | self.midplane_ip = '192.168.1.100' 72 | else: 73 | self.midplane_ip = '192.168.1.{}'.format(self.get_slot()) 74 | 75 | def module_pre_shutdown(self): 76 | pass 77 | 78 | def module_post_startup(self): 79 | pass 80 | 81 | def set_admin_state_gracefully(self, up): 82 | """Mock implementation of set_admin_state_gracefully""" 83 | return self.set_admin_state(up) 84 | 85 | def clear_module_state_transition(self, module_name): 86 | """Mock implementation of clear_module_state_transition""" 87 | return True 88 | 89 | def clear_module_gnoi_halt_in_progress(self): 90 | """Mock implementation of clear_module_gnoi_halt_in_progress""" 91 | return True 92 | 93 | def is_midplane_reachable(self): 94 | return self.midplane_access 95 | 96 | def set_midplane_reachable(self, up): 97 | self.midplane_access = up 98 | 99 | def get_all_asics(self): 100 | return self.asic_list 101 | 102 | def get_reboot_cause(self): 103 | return 'reboot', 'N/A' 104 | 105 | def get_serial(self): 106 | return self.module_serial 107 | 108 | def set_serial(self, serial): 109 | self.serial = serial 110 | 111 | def set_replaceable(self, replaceable): 112 | self.replaceable = replaceable 113 | 114 | def set_model(self, model): 115 | self.model = model 116 | 117 | def set_presence(self, presence): 118 | self.presence = presence 119 | 120 | class MockChassis: 121 | def __init__(self): 122 | self.module_list = [] 123 | self.midplane_supervisor_access = False 124 | self._is_smartswitch = False 125 | 126 | def get_num_modules(self): 127 | return len(self.module_list) 128 | 129 | def get_module(self, index): 130 | module = self.module_list[index] 131 | return module 132 | 133 | def get_all_modules(self): 134 | return self.module_list 135 | 136 | def get_module_index(self, module_name): 137 | for module in self.module_list: 138 | if module.module_name == module_name: 139 | return module.module_index 140 | return -1 141 | 142 | def init_midplane_switch(self): 143 | return True 144 | 145 | def get_serial(self): 146 | return "Serial No" 147 | 148 | def get_model(self): 149 | return "Model A" 150 | 151 | def get_revision(self): 152 | return "Rev C" 153 | 154 | def is_smartswitch(self): 155 | return self._is_smartswitch 156 | 157 | def get_my_slot(self): 158 | return 1 159 | 160 | def get_supervisor_slot(self): 161 | return 0 162 | 163 | class MockSmartSwitchChassis: 164 | def __init__(self): 165 | self.module_list = [] 166 | self.midplane_supervisor_access = False 167 | self._is_smartswitch = True 168 | 169 | def get_num_modules(self): 170 | return len(self.module_list) 171 | 172 | def get_module(self, index): 173 | module = self.module_list[index] 174 | return module 175 | 176 | def get_all_modules(self): 177 | return self.module_list 178 | 179 | def get_module_index(self, module_name): 180 | for module in self.module_list: 181 | if module.module_name == module_name: 182 | return module.module_index 183 | return -1 184 | 185 | def init_midplane_switch(self): 186 | return True 187 | 188 | def get_serial(self): 189 | return "Serial No" 190 | 191 | def get_model(self): 192 | return "Model A" 193 | 194 | def get_revision(self): 195 | return "Rev C" 196 | 197 | def is_smartswitch(self): 198 | return self._is_smartswitch 199 | 200 | def get_dataplane_state(self): 201 | raise NotImplementedError 202 | 203 | def get_controlplane_state(self): 204 | raise NotImplementedError 205 | 206 | class MockDpuChassis: 207 | 208 | def get_dpu_id(self): 209 | return 0 210 | 211 | def get_dataplane_state(self): 212 | raise NotImplementedError 213 | 214 | def get_controlplane_state(self): 215 | raise NotImplementedError 216 | -------------------------------------------------------------------------------- /sonic-chassisd/tests/mock_swsscommon.py: -------------------------------------------------------------------------------- 1 | STATE_DB = '' 2 | 3 | 4 | class Table: 5 | def __init__(self, *argv): 6 | self.db_or_pipe = argv[0] 7 | self.table_name = argv[1] 8 | self.mock_dict = {} 9 | 10 | def _del(self, key): 11 | if key in self.mock_dict: 12 | del self.mock_dict[key] 13 | pass 14 | 15 | def set(self, key, fvs): 16 | if isinstance(fvs, list): 17 | self.mock_dict[key] = dict(fvs) 18 | elif hasattr(fvs, 'fv_dict'): 19 | self.mock_dict[key] = fvs.fv_dict 20 | else: 21 | raise ValueError("Unsupported format for field-value pairs.") 22 | 23 | def get(self, key): 24 | if key in self.mock_dict: 25 | return [True, tuple(self.mock_dict[key].items())] 26 | return None 27 | 28 | def hget(self, key, field): 29 | if key not in self.mock_dict or field not in self.mock_dict[key]: 30 | return [False, None] 31 | 32 | return [True, self.mock_dict[key][field]] 33 | 34 | def hset(self, key, field, value): 35 | if key not in self.mock_dict: 36 | self.mock_dict[key] = {} 37 | 38 | self.mock_dict[key][field] = value 39 | 40 | def hdel(self, key, field): 41 | if key not in self.mock_dict or field not in self.mock_dict: 42 | return 43 | 44 | del self.mock_dict[key][field] 45 | 46 | def getKeys(self): 47 | return list(self.mock_dict) 48 | 49 | def size(self): 50 | return len(self.mock_dict) 51 | 52 | class FieldValuePairs: 53 | def __init__(self, fvs): 54 | self.fv_dict = dict(fvs) 55 | pass 56 | 57 | class Select: 58 | OBJECT = 0 59 | TIMEOUT = 1 60 | 61 | def addSelectable(self, selectable): 62 | pass 63 | 64 | def removeSelectable(self, selectable): 65 | pass 66 | 67 | def select(self, timeout=-1, interrupt_on_signal=False): 68 | return self.TIMEOUT, None 69 | 70 | class SubscriberStateTable(Table): 71 | 72 | def pop(self): 73 | return None 74 | 75 | def pops(self): 76 | return None 77 | 78 | def getDbConnector(self): 79 | return MockDbConnector() 80 | 81 | 82 | class MockDbConnector: 83 | 84 | def getDbName(self): 85 | return 'CHASSIS_STATE_DB' 86 | 87 | class RedisPipeline: 88 | def __init__(self, db): 89 | self.db = db 90 | 91 | def loadRedisScript(self, script): 92 | self.script = script 93 | self.script_mock_sha = 'd79033d1cab85249929e8c069f6784474d71cc43' 94 | return self.script_mock_sha 95 | 96 | class ConfigDBConnector: 97 | 98 | def connect(*args, **kwargs): 99 | pass 100 | 101 | def get_table(*args, **kwargs): 102 | pass 103 | -------------------------------------------------------------------------------- /sonic-chassisd/tests/mocked_libs/sonic_platform/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mock implementation of sonic_platform package for unit testing 3 | """ 4 | 5 | from . import chassis 6 | from . import platform 7 | -------------------------------------------------------------------------------- /sonic-chassisd/tests/mocked_libs/sonic_platform/chassis.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mock implementation of sonic_platform package for unit testing 3 | """ 4 | 5 | import sys 6 | if sys.version_info.major == 3: 7 | from unittest import mock 8 | else: 9 | import mock 10 | 11 | from sonic_platform_base.chassis_base import ChassisBase 12 | 13 | 14 | class Chassis(ChassisBase): 15 | def __init__(self): 16 | ChassisBase.__init__(self) 17 | self.eeprom = mock.MagicMock() 18 | 19 | def get_eeprom(self): 20 | return self.eeprom 21 | 22 | def get_my_slot(self): 23 | return 1 24 | 25 | def get_supervisor_slot(self): 26 | return 1 27 | -------------------------------------------------------------------------------- /sonic-chassisd/tests/mocked_libs/sonic_platform/platform.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mock implementation of sonic_platform package for unit testing 3 | """ 4 | 5 | from sonic_platform_base.platform_base import PlatformBase 6 | from sonic_platform.chassis import Chassis 7 | 8 | 9 | class Platform(PlatformBase): 10 | def __init__(self): 11 | PlatformBase.__init__(self) 12 | self._chassis = Chassis() 13 | -------------------------------------------------------------------------------- /sonic-chassisd/tests/test_chassis_db_init.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from imp import load_source 4 | 5 | from mock import Mock, MagicMock, patch 6 | from sonic_py_common import daemon_base 7 | 8 | from .mock_platform import MockChassis, MockModule 9 | from .mock_module_base import ModuleBase 10 | 11 | SYSLOG_IDENTIFIER = 'chassis_db_init_test' 12 | NOT_AVAILABLE = 'N/A' 13 | 14 | daemon_base.db_connect = MagicMock() 15 | 16 | test_path = os.path.dirname(os.path.abspath(__file__)) 17 | modules_path = os.path.dirname(test_path) 18 | scripts_path = os.path.join(modules_path, "scripts") 19 | sys.path.insert(0, modules_path) 20 | 21 | os.environ["CHASSIS_DB_INIT_UNIT_TESTING"] = "1" 22 | load_source('chassis_db_init', scripts_path + '/chassis_db_init') 23 | from chassis_db_init import * 24 | 25 | 26 | def test_provision_db(): 27 | chassis = MockChassis() 28 | log = MagicMock() 29 | serial = "Serial No" 30 | model = "Model A" 31 | revision = "Rev C" 32 | 33 | chassis_table = provision_db(chassis, log) 34 | 35 | fvs = chassis_table.get(CHASSIS_INFO_KEY_TEMPLATE.format(1)) 36 | if isinstance(fvs, list): 37 | fvs = dict(fvs[-1]) 38 | assert serial == fvs[CHASSIS_INFO_SERIAL_FIELD] 39 | assert model == fvs[CHASSIS_INFO_MODEL_FIELD] 40 | assert revision == fvs[CHASSIS_INFO_REV_FIELD] 41 | -------------------------------------------------------------------------------- /sonic-ledd/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml --junitxml=test-results.xml -v 3 | -------------------------------------------------------------------------------- /sonic-ledd/scripts/ledd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | ledd 5 | Front-panel LED control daemon for SONiC 6 | """ 7 | 8 | import getopt 9 | import sys 10 | 11 | from sonic_py_common import daemon_base 12 | from sonic_py_common import multi_asic 13 | from swsscommon import swsscommon 14 | 15 | #============================= Constants ============================= 16 | 17 | VERSION = '2.0' 18 | 19 | SYSLOG_IDENTIFIER = "ledd" 20 | 21 | USAGE_HELP = """ 22 | Usage: ledd [options] 23 | 24 | Options: 25 | -h,--help Print this usage statement and exit 26 | -v,--version Print version information and exit 27 | """ 28 | 29 | LED_MODULE_NAME = "led_control" 30 | LED_CLASS_NAME = "LedControl" 31 | 32 | SELECT_TIMEOUT = 1000 33 | 34 | LEDUTIL_LOAD_ERROR = 1 35 | LEDUTIL_RUNTIME_ERROR = 2 36 | LEDD_SELECT_ERROR = 3 37 | 38 | MAX_FRONT_PANEL_PORTS = 256 39 | 40 | class Port(): 41 | PORT_UP = "up" # All subports are up 42 | PORT_DOWN = "down" # All subports are down 43 | PORT_OP_FIELD = "netdev_oper_status" 44 | 45 | def __init__(self, name, index, state, subport, role): 46 | self._name = name 47 | self._index = index 48 | self._state = state 49 | self._subport = subport 50 | self._role = role 51 | 52 | def __str__(self): 53 | return "Port(name={}, index={}, state={}, subport={}, role={})".format( 54 | self._name, self._index, self._state, self._subport, self._role) 55 | 56 | def isFrontPanelPort(self): 57 | return multi_asic.is_front_panel_port(self._name, self._role) 58 | 59 | class FrontPanelPorts: 60 | def __init__(self, fp_list, up_subports, logical_pmap, led_ctrl): 61 | # {port-index, total subports oper UP} 62 | self.fp_port_up_subports = up_subports 63 | # {port-index, list of logical ports} 64 | self.fp_port_list = fp_list 65 | self.logical_port_mapping = logical_pmap 66 | self.led_control = led_ctrl 67 | 68 | def initPortLeds(self): 69 | """ 70 | Initialize the port LEDs based on the current state of the front panel ports 71 | """ 72 | for index in range(MAX_FRONT_PANEL_PORTS): 73 | if len(self.fp_port_list[index]) > 0: 74 | name = next(iter(self.fp_port_list[index])) 75 | if self.areAllSubportsUp(name): 76 | self.updatePortLed(name, Port.PORT_UP) 77 | else: 78 | self.updatePortLed(name, Port.PORT_DOWN) 79 | 80 | def updatePortLed(self, port_name, port_state): 81 | try: 82 | self.led_control.port_link_state_change(port_name, port_state) 83 | except Exception as e: 84 | sys.exit(LEDUTIL_RUNTIME_ERROR) 85 | 86 | def getPort(self, name): 87 | if name in self.logical_port_mapping: 88 | port = self.logical_port_mapping[name] 89 | return port 90 | return None 91 | 92 | def areAllSubportsUp(self, name): 93 | port = self.getPort(name) 94 | if port: 95 | return self.fp_port_up_subports[port._index] == self.getTotalSubports(port._index) 96 | 97 | return False 98 | 99 | def areAllSubportsDown(self, name): 100 | port = self.getPort(name) 101 | if port: 102 | return self.fp_port_up_subports[port._index] == 0 103 | 104 | return True 105 | 106 | def getTotalSubports(self, index): 107 | if index < MAX_FRONT_PANEL_PORTS: 108 | return len(self.fp_port_list[index]) 109 | return 0 110 | 111 | def updatePortState(self, port_name, port_state): 112 | """ 113 | Return True if the port state has changed, False otherwise 114 | """ 115 | assert port_state in [Port.PORT_UP, Port.PORT_DOWN] 116 | port = self.getPort(port_name) 117 | if port and port_state != port._state: 118 | if port_state == Port.PORT_UP: 119 | self.fp_port_up_subports[port._index] = min(1 + self.fp_port_up_subports[port._index], 120 | self.getTotalSubports(port._index)) 121 | else: 122 | self.fp_port_up_subports[port._index] = max(0, self.fp_port_up_subports[port._index] - 1) 123 | port._state = port_state 124 | return True 125 | return False 126 | 127 | class PortStateObserver: 128 | def __init__(self): 129 | # Subscribe to PORT table notifications in the STATE DB 130 | self.tables = {} 131 | self.sel = swsscommon.Select() 132 | 133 | def subscribePortTable(self, namespaces): 134 | for namespace in namespaces: 135 | self.subscribeDbTable("STATE_DB", swsscommon.STATE_PORT_TABLE_NAME, namespace) 136 | 137 | def connectDB(self, dbname, namespace): 138 | db = daemon_base.db_connect(dbname, namespace=namespace) 139 | return db 140 | 141 | def getDatabaseTable(self, dbname, tblname, namespace): 142 | db = self.connectDB(dbname, namespace) 143 | table = swsscommon.Table(db, tblname) 144 | return table 145 | 146 | def subscribeDbTable(self, dbname, tblname, namespace): 147 | db = self.connectDB(dbname, namespace) 148 | self.tables[namespace] = swsscommon.SubscriberStateTable(db, tblname) 149 | self.sel.addSelectable(self.tables[namespace]) 150 | 151 | def getSelectEvent(self, timeout=SELECT_TIMEOUT): 152 | return self.sel.select(timeout) 153 | 154 | def getPortTableEvent(self, selectableObj): 155 | redisSelectObj = swsscommon.CastSelectableToRedisSelectObj(selectableObj) 156 | namespace = redisSelectObj.getDbConnector().getNamespace() 157 | 158 | (key, op, fvp) = self.tables[namespace].pop() 159 | if not key: 160 | return None 161 | 162 | if fvp: 163 | if key in ["PortConfigDone", "PortInitDone"]: 164 | return None 165 | 166 | fvp_dict = dict(fvp) 167 | if op == "SET" and Port.PORT_OP_FIELD in fvp_dict: 168 | return (key, fvp_dict[Port.PORT_OP_FIELD]) 169 | 170 | return None 171 | 172 | class DaemonLedd(daemon_base.DaemonBase): 173 | def __init__(self): 174 | daemon_base.DaemonBase.__init__(self, SYSLOG_IDENTIFIER) 175 | 176 | if multi_asic.is_multi_asic(): 177 | # Load the namespace details first from the database_global.json file. 178 | swsscommon.SonicDBConfig.initializeGlobalConfig() 179 | 180 | # Load platform-specific LedControl module 181 | try: 182 | self.led_control = self.load_platform_util(LED_MODULE_NAME, LED_CLASS_NAME) 183 | except Exception as e: 184 | self.log_error("Failed to load ledutil: %s" % (str(e)), True) 185 | sys.exit(LEDUTIL_LOAD_ERROR) 186 | 187 | 188 | # Initialize the PortStateObserver 189 | self.portObserver = PortStateObserver() 190 | 191 | # subscribe to all the front panel ports namespaces 192 | namespaces = multi_asic.get_front_end_namespaces() 193 | self.portObserver.subscribePortTable(namespaces) 194 | 195 | # Discover the front panel ports 196 | fp_plist, fp_ups, lmap = self.findFrontPanelPorts(namespaces) 197 | self.fp_ports = FrontPanelPorts(fp_plist, fp_ups, lmap, self.led_control) 198 | 199 | # Initialize the port LEDs color 200 | self.fp_ports.initPortLeds() 201 | 202 | def findFrontPanelPorts(self, namespaces): 203 | # {port-index, list of logical ports} 204 | fp_port_list = [set() for _ in range(MAX_FRONT_PANEL_PORTS)] 205 | # {port-index, total subports oper UP} 206 | fp_port_up_subports = [0] * MAX_FRONT_PANEL_PORTS 207 | logical_port_mapping = {} 208 | 209 | for namespace in namespaces: 210 | port_cfg_table = self.portObserver.getDatabaseTable("CONFIG_DB", swsscommon.CFG_PORT_TABLE_NAME, namespace) 211 | port_st_table = self.portObserver.getDatabaseTable("STATE_DB", swsscommon.STATE_PORT_TABLE_NAME, namespace) 212 | for key in port_cfg_table.getKeys(): 213 | _, pcfg = port_cfg_table.get(key) 214 | _, pstate = port_st_table.get(key) 215 | pcfg_dict = dict(pcfg) 216 | pstate_dict = dict(pstate) 217 | p = Port(key, 218 | int(pcfg_dict['index']), 219 | pstate_dict.get(Port.PORT_OP_FIELD, Port.PORT_DOWN), # Current oper state 220 | pcfg_dict.get('subport', 0), 221 | pcfg_dict.get('role', None)) 222 | if p.isFrontPanelPort(): 223 | logical_port_mapping[key] = p 224 | fp_port_list[p._index].add(key) 225 | if p._state == Port.PORT_UP: 226 | fp_port_up_subports[p._index] += 1 227 | return fp_port_list, fp_port_up_subports, logical_port_mapping 228 | 229 | def processPortStateChange(self, port_name, port_state): 230 | if self.fp_ports.getPort(port_name): 231 | # Update the port state for front panel ports 232 | if self.fp_ports.updatePortState(port_name, port_state): 233 | if self.fp_ports.areAllSubportsUp(port_name): 234 | state = Port.PORT_UP 235 | else: 236 | state = Port.PORT_DOWN 237 | self.log_notice("Setting Port %s LED state change for %s" % (port_name, state)) 238 | self.fp_ports.updatePortLed(port_name, state) 239 | # Run daemon 240 | def run(self): 241 | state, event = self.portObserver.getSelectEvent() 242 | 243 | if state == swsscommon.Select.TIMEOUT: 244 | # Process final state 245 | return 0 246 | 247 | if state != swsscommon.Select.OBJECT: 248 | self.log_warning("sel.select() did not return swsscommon.Select.OBJECT - May be socket closed???") 249 | return -1 ## Fail here so that the daemon can be restarted 250 | 251 | portEvent = self.portObserver.getPortTableEvent(event) 252 | if portEvent: 253 | self.log_notice("Received PORT table event: key=%s, state=%s" % (portEvent[0], portEvent[1])) 254 | self.processPortStateChange(portEvent[0], portEvent[1]) 255 | 256 | return 0 257 | 258 | def main(): 259 | # Parse options if provided 260 | if len(sys.argv) > 1: 261 | try: 262 | (options, remainder) = getopt.getopt(sys.argv[1:], 'hv', ['help', 'version']) 263 | except getopt.GetoptError as e: 264 | print(e) 265 | print(USAGE_HELP) 266 | sys.exit(1) 267 | 268 | for opt, arg in options: 269 | if opt == '--help' or opt == '-h': 270 | print(USAGE_HELP) 271 | sys.exit(0) 272 | elif opt == '--version' or opt == '-v': 273 | print('ledd version {}'.format(VERSION)) 274 | sys.exit(0) 275 | 276 | ledd = DaemonLedd() 277 | 278 | # Listen indefinitely for port oper status changes 279 | while True: 280 | if 0 != ledd.run(): 281 | print("ledd.run() failed... Exiting") 282 | sys.exit(LEDD_SELECT_ERROR) 283 | 284 | if __name__ == '__main__': 285 | main() 286 | -------------------------------------------------------------------------------- /sonic-ledd/setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | -------------------------------------------------------------------------------- /sonic-ledd/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='sonic-ledd', 5 | version='1.1', 6 | description='Front-panel LED control daemon for SONiC', 7 | license='Apache 2.0', 8 | author='SONiC Team', 9 | author_email='linuxnetdev@microsoft.com', 10 | url='https://github.com/Azure/sonic-platform-daemons', 11 | maintainer='Prince George', 12 | maintainer_email='prgeor@microsoft.com', 13 | scripts=[ 14 | 'scripts/ledd', 15 | ], 16 | setup_requires=[ 17 | 'wheel' 18 | ], 19 | tests_require=[ 20 | 'mock>=2.0.0; python_version < "3.3"', 21 | 'pytest', 22 | 'pytest-cov' 23 | ], 24 | classifiers=[ 25 | 'Development Status :: 4 - Beta', 26 | 'Environment :: No Input/Output (Daemon)', 27 | 'Intended Audience :: Developers', 28 | 'Intended Audience :: Information Technology', 29 | 'Intended Audience :: System Administrators', 30 | 'License :: OSI Approved :: Apache Software License', 31 | 'Natural Language :: English', 32 | 'Operating System :: POSIX :: Linux', 33 | 'Programming Language :: Python :: 2.7', 34 | 'Topic :: System :: Hardware', 35 | ], 36 | keywords='sonic SONiC LED led daemon LEDD ledd', 37 | ) 38 | -------------------------------------------------------------------------------- /sonic-ledd/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-ledd/tests/__init__.py -------------------------------------------------------------------------------- /sonic-ledd/tests/mock_swsscommon.py: -------------------------------------------------------------------------------- 1 | STATE_DB = '' 2 | 3 | 4 | class Table: 5 | def __init__(self, db, table_name): 6 | self.table_name = table_name 7 | self.mock_dict = {} 8 | 9 | def _del(self, key): 10 | del self.mock_dict[key] 11 | pass 12 | 13 | def set(self, key, fvs): 14 | self.mock_dict[key] = fvs.fv_dict 15 | pass 16 | 17 | def get(self, key): 18 | if key in self.mock_dict: 19 | return self.mock_dict[key] 20 | return None 21 | 22 | 23 | class FieldValuePairs(dict): 24 | def __init__(self, len): 25 | self.fv_dict = {} 26 | 27 | def __setitem__(self, key, val_tuple): 28 | self.fv_dict[val_tuple[0]] = val_tuple[1] 29 | -------------------------------------------------------------------------------- /sonic-pcied/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml --junitxml=test-results.xml -vv 3 | -------------------------------------------------------------------------------- /sonic-pcied/scripts/pcied: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | pcied 5 | PCIe device monitoring daemon for SONiC 6 | """ 7 | 8 | import os 9 | import signal 10 | import sys 11 | import threading 12 | 13 | from sonic_py_common import daemon_base, device_info, logger 14 | from swsscommon import swsscommon 15 | 16 | # 17 | # Constants ==================================================================== 18 | # 19 | 20 | # TODO: Once we no longer support Python 2, we can eliminate this and get the 21 | # name using the 'name' field (e.g., `signal.SIGINT.name`) starting with Python 3.5 22 | SIGNALS_TO_NAMES_DICT = dict((getattr(signal, n), n) 23 | for n in dir(signal) if n.startswith('SIG') and '_' not in n) 24 | 25 | SYSLOG_IDENTIFIER = "pcied" 26 | 27 | PCIE_RESULT_REGEX = "PCIe Device Checking All Test" 28 | PCIE_DEVICE_TABLE_NAME = "PCIE_DEVICE" 29 | PCIE_STATUS_TABLE_NAME = "PCIE_DEVICES" 30 | PCIE_DETACH_INFO_TABLE = "PCIE_DETACH_INFO" 31 | 32 | PCIE_DETACH_BUS_INFO_FIELD = "bus_info" 33 | PCIE_DETACH_DPU_STATE_FIELD = "dpu_state" 34 | 35 | PCIED_MAIN_THREAD_SLEEP_SECS = 60 36 | 37 | PCIEUTIL_CONF_FILE_ERROR = 1 38 | PCIEUTIL_LOAD_ERROR = 2 39 | 40 | platform_pcieutil = None 41 | 42 | log = logger.Logger(SYSLOG_IDENTIFIER) 43 | 44 | exit_code = 0 45 | 46 | # wrapper functions to call the platform api 47 | def load_platform_pcieutil(): 48 | _platform_pcieutil = None 49 | 50 | try: 51 | (platform_path, _) = device_info.get_paths_to_platform_and_hwsku_dirs() 52 | except (OSError, TypeError) as e: 53 | log.log_error(f"Unable to get device info from Platform : {type(e).__name__} : {e}") 54 | return _platform_pcieutil 55 | 56 | try: 57 | from sonic_platform.pcie import Pcie 58 | _platform_pcieutil = Pcie(platform_path) 59 | except ImportError as e: 60 | log.log_notice("Failed to load platform Pcie module. Error : {}, Fallback to default module".format(str(e)), True) 61 | try: 62 | from sonic_platform_base.sonic_pcie.pcie_common import PcieUtil 63 | _platform_pcieutil = PcieUtil(platform_path) 64 | except ImportError as e: 65 | log.log_error("Failed to load default PcieUtil module. Error : {}".format(str(e)), True) 66 | if _platform_pcieutil is None: 67 | log.log_error("Failed to load any PCIe utility module. Exiting...", True) 68 | raise RuntimeError("Unable to load PCIe utility module.") 69 | return _platform_pcieutil 70 | 71 | def read_id_file(device_name): 72 | id = None 73 | dev_id_path = '/sys/bus/pci/devices/0000:%s/device' % device_name 74 | 75 | if os.path.exists(dev_id_path): 76 | with open(dev_id_path, 'r') as fd: 77 | id = fd.read().strip() 78 | return id 79 | 80 | # 81 | # Daemon ======================================================================= 82 | # 83 | 84 | 85 | class DaemonPcied(daemon_base.DaemonBase): 86 | def __init__(self, log_identifier): 87 | super(DaemonPcied, self).__init__(log_identifier) 88 | 89 | self.timeout = PCIED_MAIN_THREAD_SLEEP_SECS 90 | self.stop_event = threading.Event() 91 | self.state_db = None 92 | self.device_table = None 93 | self.status_table = None 94 | self.resultInfo = [] 95 | self.device_name = None 96 | self.aer_stats = {} 97 | 98 | global platform_pcieutil 99 | 100 | platform_pcieutil = load_platform_pcieutil() 101 | if platform_pcieutil is None: 102 | sys.exit(PCIEUTIL_LOAD_ERROR) 103 | 104 | # Connect to STATE_DB and create pcie device table 105 | try: 106 | self.state_db = daemon_base.db_connect("STATE_DB") 107 | self.device_table = swsscommon.Table(self.state_db, PCIE_DEVICE_TABLE_NAME) 108 | self.status_table = swsscommon.Table(self.state_db, PCIE_STATUS_TABLE_NAME) 109 | self.detach_info = swsscommon.Table(self.state_db, PCIE_DETACH_INFO_TABLE) 110 | except Exception as e: 111 | log.log_error("Failed to connect to STATE_DB or create table. Error: {}".format(str(e)), True) 112 | sys.exit(PCIEUTIL_CONF_FILE_ERROR) 113 | 114 | def __del__(self): 115 | try: 116 | if self.device_table: 117 | table_keys = self.device_table.getKeys() 118 | for tk in table_keys: 119 | self.device_table._del(tk) 120 | if self.status_table: 121 | stable_keys = self.status_table.getKeys() 122 | for stk in stable_keys: 123 | self.status_table._del(stk) 124 | except Exception as e: 125 | log.log_warning("Exception during cleanup: {}".format(str(e)), True) 126 | 127 | # load aer-fields into statedb 128 | def update_aer_to_statedb(self): 129 | if self.aer_stats is None: 130 | self.log_debug("PCIe device {} has no AER Stats".format(self.device_name)) 131 | return 132 | 133 | try: 134 | aer_fields = { 135 | f"{key}|{field}": value 136 | for key, fv in self.aer_stats.items() 137 | for field, value in fv.items() 138 | } 139 | if aer_fields: 140 | self.device_table.set(self.device_name, swsscommon.FieldValuePairs(list(aer_fields.items()))) 141 | else: 142 | self.log_debug("PCIe device {} has no AER attributes".format(self.device_name)) 143 | except Exception as e: 144 | self.log_error("Exception while updating AER attributes to STATE_DB for {}: {}".format(self.device_name, str(e))) 145 | 146 | 147 | # Check the PCIe AER Stats 148 | def check_n_update_pcie_aer_stats(self, Bus, Dev, Fn): 149 | try: 150 | self.device_name = "%02x:%02x.%d" % (Bus, Dev, Fn) 151 | Id = read_id_file(self.device_name) 152 | self.aer_stats = {} 153 | if Id is not None: 154 | self.device_table.set(self.device_name, swsscommon.FieldValuePairs([('id', Id)])) 155 | self.aer_stats = platform_pcieutil.get_pcie_aer_stats(bus=Bus, dev=Dev, func=Fn) 156 | self.update_aer_to_statedb() 157 | except Exception as e: 158 | self.log_error("Exception while checking AER attributes for {}: {}".format(self.device_name, str(e))) 159 | 160 | 161 | # Update the PCIe devices status to DB 162 | def update_pcie_devices_status_db(self, err): 163 | if err: 164 | pcie_status = "FAILED" 165 | self.log_error("PCIe device status check : {}".format(pcie_status)) 166 | else: 167 | pcie_status = "PASSED" 168 | self.log_info("PCIe device status check : {}".format(pcie_status)) 169 | 170 | try: 171 | self.status_table.set("status", swsscommon.FieldValuePairs([('status', pcie_status)])) 172 | except Exception as e: 173 | self.log_error("Exception while updating PCIe device status to STATE_DB: {}".format(str(e))) 174 | 175 | # Check if any PCI interface is in detaching mode by querying the state_db 176 | def is_dpu_in_detaching_mode(self, pcie_dev): 177 | # Ensure detach_info is not None 178 | if self.detach_info is None: 179 | self.log_debug("detach_info is None") 180 | return False 181 | 182 | # Query the state_db for the device detaching status 183 | detach_info_keys = list(self.detach_info.getKeys()) 184 | if not detach_info_keys: 185 | return False 186 | 187 | for key in detach_info_keys: 188 | dpu_info = self.detach_info.get(key) 189 | if dpu_info: 190 | # Convert tuple of field-value pairs to dictionary for easier access 191 | dpu_dict = dict(dpu_info[1]) 192 | bus_info = dpu_dict.get(PCIE_DETACH_BUS_INFO_FIELD) 193 | dpu_state = dpu_dict.get(PCIE_DETACH_DPU_STATE_FIELD) 194 | if bus_info == pcie_dev and dpu_state == "detaching": 195 | return True 196 | 197 | return False 198 | 199 | # Check the PCIe devices 200 | def check_pcie_devices(self): 201 | self.resultInfo = platform_pcieutil.get_pcie_check() 202 | err = 0 203 | if self.resultInfo is None: 204 | return 205 | 206 | for result in self.resultInfo: 207 | if result["result"] == "Failed": 208 | # Convert bus, device, and function to a bus_info format like "0000:03:00.0" 209 | pcie_dev = "0000:{:02x}:{:02x}.{}".format(int(result["bus"], 16), int(result["dev"], 16), int(result["fn"], 16)) 210 | 211 | # Check if the device is in detaching mode 212 | if device_info.is_smartswitch() and self.is_dpu_in_detaching_mode(pcie_dev): 213 | self.log_debug("PCIe Device: {} is in detaching mode, skipping warning.".format(pcie_dev)) 214 | continue 215 | 216 | self.log_warning("PCIe Device: " + result["name"] + " Not Found") 217 | err += 1 218 | else: 219 | Bus = int(result["bus"], 16) 220 | Dev = int(result["dev"], 16) 221 | Fn = int(result["fn"], 16) 222 | # update AER-attributes to DB 223 | self.check_n_update_pcie_aer_stats(Bus, Dev, Fn) 224 | 225 | # update PCIe Device Status to DB 226 | self.update_pcie_devices_status_db(err) 227 | 228 | # Override signal handler from DaemonBase 229 | def signal_handler(self, sig, frame): 230 | FATAL_SIGNALS = [signal.SIGINT, signal.SIGTERM] 231 | NONFATAL_SIGNALS = [signal.SIGHUP] 232 | 233 | global exit_code 234 | 235 | if sig in FATAL_SIGNALS: 236 | self.log_info("Caught signal '{}' - exiting...".format(SIGNALS_TO_NAMES_DICT[sig])) 237 | exit_code = 128 + sig # Make sure we exit with a non-zero code so that supervisor will try to restart us 238 | self.stop_event.set() 239 | elif sig in NONFATAL_SIGNALS: 240 | self.log_info("Caught signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) 241 | else: 242 | self.log_warning("Caught unhandled signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) 243 | 244 | # Main daemon logic 245 | def run(self): 246 | try: 247 | if self.stop_event.wait(self.timeout): 248 | # We received a fatal signal 249 | return False 250 | except Exception as e: 251 | self.log_error("Exception occurred during stop_event wait: {}".format(str(e))) 252 | return False 253 | 254 | self.check_pcie_devices() 255 | 256 | return True 257 | # 258 | # Main ========================================================================= 259 | # 260 | 261 | 262 | def main(): 263 | pcied = DaemonPcied(SYSLOG_IDENTIFIER) 264 | 265 | pcied.log_info("Starting up...") 266 | 267 | while pcied.run(): 268 | pass 269 | 270 | pcied.log_info("Shutting down...") 271 | 272 | return exit_code 273 | 274 | if __name__ == '__main__': 275 | sys.exit(main()) 276 | -------------------------------------------------------------------------------- /sonic-pcied/setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | -------------------------------------------------------------------------------- /sonic-pcied/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='sonic-pcied', 5 | version='1.0', 6 | description='PCIe check daemon for SONiC', 7 | license='Apache 2.0', 8 | author='SONiC Team', 9 | author_email='linuxnetdev@microsoft.com', 10 | url='https://github.com/Azure/sonic-platform-daemons', 11 | maintainer='Sujin Kang', 12 | maintainer_email='sujkang@microsoft.com', 13 | scripts=[ 14 | 'scripts/pcied', 15 | ], 16 | setup_requires=[ 17 | 'pytest-runner', 18 | 'wheel' 19 | ], 20 | install_requires=[ 21 | 'enum34; python_version < "3.4"', 22 | 'sonic-py-common', 23 | ], 24 | tests_require=[ 25 | 'mock>=2.0.0; python_version < "3.3"', 26 | 'pytest', 27 | 'pytest-cov', 28 | ], 29 | classifiers=[ 30 | 'Development Status :: 4 - Beta', 31 | 'Environment :: No Input/Output (Daemon)', 32 | 'Intended Audience :: Developers', 33 | 'Intended Audience :: Information Technology', 34 | 'Intended Audience :: System Administrators', 35 | 'License :: OSI Approved :: Apache Software License', 36 | 'Natural Language :: English', 37 | 'Operating System :: POSIX :: Linux', 38 | 'Programming Language :: Python :: 2.7', 39 | 'Topic :: System :: Hardware', 40 | ], 41 | keywords='sonic SONiC PCIe pcie PCIED pcied', 42 | test_suite='setup.get_test_suite' 43 | ) 44 | -------------------------------------------------------------------------------- /sonic-pcied/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-pcied/tests/__init__.py -------------------------------------------------------------------------------- /sonic-pcied/tests/mock_platform.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Mock implementation of sonic_platform package for unit testing 4 | """ 5 | 6 | # TODO: Clean this up once we no longer need to support Python 2 7 | import sys 8 | if sys.version_info.major == 3: 9 | from unittest import mock 10 | else: 11 | import mock 12 | 13 | 14 | 15 | pcie_device_list = \ 16 | """ 17 | [{'bus': '00', 'dev': '01', 'fn': '0', 'id': '1f10', 'name': 'PCI A'}] 18 | """ 19 | 20 | pcie_check_result = \ 21 | """ 22 | [{'bus': '00', 'dev': '01', 'fn': '0', 'id': '1f10', 'name': 'PCI A', 'result': 'Passed'}] 23 | """ 24 | 25 | pcie_aer_stats = \ 26 | """ 27 | {'correctable': {}, 'fatal': {}, 'non_fatal': {}} 28 | """ 29 | 30 | #class MockPcieUtil(PcieUtil): 31 | class MockPcieUtil(): 32 | def __init__(self, 33 | pciList=pcie_device_list, 34 | result=pcie_check_result, 35 | aer_stats=pcie_aer_stats): 36 | super(MockPcieUtil, self).__init__() 37 | self._pciList = pciList 38 | self._result = result 39 | self._aer_stats = aer_stats 40 | 41 | def get_pcie_device(self): 42 | return self._pciList 43 | 44 | def get_pcie_check(self): 45 | return self._result 46 | 47 | def get_pcie_aer_stats(self, domain, bus, dev, fn): 48 | return self._aer_stats 49 | -------------------------------------------------------------------------------- /sonic-pcied/tests/mocked_libs/sonic_platform/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mock implementation of sonic_platform package for unit testing 3 | """ 4 | 5 | from . import pcie 6 | 7 | -------------------------------------------------------------------------------- /sonic-pcied/tests/mocked_libs/sonic_platform/pcie.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mock implementation of sonic_platform package for unit testing 3 | """ 4 | 5 | from sonic_platform_base.sonic_pcie.pcie_base import PcieBase 6 | 7 | 8 | class Pcie(PcieBase): 9 | def __init__(self): 10 | self.platform_pcieutil = "/tmp/Pcie" 11 | 12 | def __str__(self): 13 | return self.platform_pcieutil 14 | -------------------------------------------------------------------------------- /sonic-pcied/tests/mocked_libs/sonic_platform_base/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sonic-pcied/tests/mocked_libs/sonic_platform_base/sonic_pcie/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-pcied/tests/mocked_libs/sonic_platform_base/sonic_pcie/__init__.py -------------------------------------------------------------------------------- /sonic-pcied/tests/mocked_libs/sonic_platform_base/sonic_pcie/pcie_base.py: -------------------------------------------------------------------------------- 1 | # 2 | # pcie_base.py 3 | # 4 | # Abstract base class for implementing platform-specific 5 | # PCIE functionality for SONiC 6 | # 7 | 8 | try: 9 | import abc 10 | except ImportError as e: 11 | raise ImportError(str(e) + " - required module not found") 12 | 13 | 14 | class PcieBase(object): 15 | def __init__(self, path): 16 | """ 17 | Constructor 18 | 19 | Args: 20 | pcieutil file and config file path 21 | """ 22 | 23 | @abc.abstractmethod 24 | def get_pcie_device(self): 25 | """ 26 | get current device pcie info 27 | 28 | Returns: 29 | A list including pcie device info 30 | """ 31 | return [] 32 | 33 | @abc.abstractmethod 34 | def get_pcie_check(self): 35 | """ 36 | Check Pcie device with config file 37 | 38 | Returns: 39 | A list including pcie device and test result info 40 | """ 41 | return [] 42 | 43 | @abc.abstractmethod 44 | def get_pcie_aer_stats(self, domain, bus, dev, fn): 45 | """ 46 | Returns a nested dictionary containing the AER stats belonging to a 47 | PCIe device 48 | 49 | Args: 50 | domain, bus, dev, fn: Domain, bus, device, function of the PCIe 51 | device respectively 52 | 53 | Returns: 54 | A nested dictionary where key is severity 'correctable', 'fatal' or 55 | 'non_fatal', value is a dictionary of key, value pairs in the format: 56 | {'AER Error type': Error count} 57 | 58 | Ex. {'correctable': {'BadDLLP': 0, 'BadTLP': 0}, 59 | 'fatal': {'RxOF': 0, 'MalfTLP': 0}, 60 | 'non_fatal': {'RxOF': 0, 'MalfTLP': 0}} 61 | 62 | For PCIe devices that do not support AER, the value for each 63 | severity key is an empty dictionary. 64 | """ 65 | return {} 66 | -------------------------------------------------------------------------------- /sonic-pcied/tests/mocked_libs/sonic_platform_base/sonic_pcie/pcie_common.py: -------------------------------------------------------------------------------- 1 | # pcie_common.py 2 | # Common PCIE check interfaces for SONIC 3 | # 4 | 5 | import os 6 | import yaml 7 | import subprocess 8 | import re 9 | import sys 10 | from copy import deepcopy 11 | try: 12 | from .pcie_base import PcieBase 13 | except ImportError as e: 14 | raise ImportError(str(e) + "- required module not found") 15 | 16 | 17 | class PcieUtil(PcieBase): 18 | """Platform-specific PCIEutil class""" 19 | # got the config file path 20 | def __init__(self, path): 21 | self.config_path = path 22 | self._conf_rev = None 23 | 24 | # load the config file 25 | def load_config_file(self): 26 | conf_rev = "_{}".format(self._conf_rev) if self._conf_rev else "" 27 | config_file = "{}/pcie{}.yaml".format(self.config_path, conf_rev) 28 | try: 29 | with open(config_file) as conf_file: 30 | self.confInfo = yaml.safe_load(conf_file) 31 | except IOError as e: 32 | print("Error: {}".format(str(e))) 33 | print("Not found config file, please add a config file manually, or generate it by running [pcieutil pcie_generate]") 34 | sys.exit() 35 | 36 | # load current PCIe device 37 | def get_pcie_device(self): 38 | pciDict = {} 39 | pciList = [] 40 | p1 = "^(\w+):(\w+)\.(\w)\s(.*)\s*\(*.*\)*" 41 | p2 = "^.*:.*:.*:(\w+)\s*\(*.*\)*" 42 | command1 = ["sudo", "lspci"] 43 | command2 = ["sudo", "lspci", "-n"] 44 | # run command 1 45 | proc1 = subprocess.Popen(command1, universal_newlines=True, stdout=subprocess.PIPE) 46 | output1 = proc1.stdout.readlines() 47 | (out, err) = proc1.communicate() 48 | # run command 2 49 | proc2 = subprocess.Popen(command2, universal_newlines=True, stdout=subprocess.PIPE) 50 | output2 = proc2.stdout.readlines() 51 | (out, err) = proc2.communicate() 52 | 53 | if proc1.returncode > 0: 54 | for line1 in output1: 55 | print(line1.strip()) 56 | return 57 | elif proc2.returncode > 0: 58 | for line2 in output2: 59 | print(line2.strip()) 60 | return 61 | else: 62 | for (line1, line2) in zip(output1, output2): 63 | pciDict.clear() 64 | match1 = re.search(p1, line1.strip()) 65 | match2 = re.search(p2, line2.strip()) 66 | if match1 and match2: 67 | Bus = match1.group(1) 68 | Dev = match1.group(2) 69 | Fn = match1.group(3) 70 | Name = match1.group(4) 71 | Id = match2.group(1) 72 | pciDict["name"] = Name 73 | pciDict["bus"] = Bus 74 | pciDict["dev"] = Dev 75 | pciDict["fn"] = Fn 76 | pciDict["id"] = Id 77 | pciList.append(pciDict) 78 | pciDict = deepcopy(pciDict) 79 | else: 80 | print("CAN NOT MATCH PCIe DEVICE") 81 | return pciList 82 | 83 | # check the sysfs tree for each PCIe device 84 | def check_pcie_sysfs(self, domain=0, bus=0, device=0, func=0): 85 | dev_path = os.path.join('/sys/bus/pci/devices', '%04x:%02x:%02x.%d' % (domain, bus, device, func)) 86 | if os.path.exists(dev_path): 87 | return True 88 | return False 89 | 90 | # check the current PCIe device with config file and return the result 91 | def get_pcie_check(self): 92 | self.load_config_file() 93 | for item_conf in self.confInfo: 94 | bus_conf = item_conf["bus"] 95 | dev_conf = item_conf["dev"] 96 | fn_conf = item_conf["fn"] 97 | if self.check_pcie_sysfs(bus=int(bus_conf, base=16), device=int(dev_conf, base=16), func=int(fn_conf, base=16)): 98 | item_conf["result"] = "Passed" 99 | else: 100 | item_conf["result"] = "Failed" 101 | return self.confInfo 102 | 103 | # return AER stats of PCIe device 104 | def get_pcie_aer_stats(self, domain=0, bus=0, dev=0, func=0): 105 | aer_stats = {'correctable': {}, 'fatal': {}, 'non_fatal': {}} 106 | dev_path = os.path.join('/sys/bus/pci/devices', '%04x:%02x:%02x.%d' % (domain, bus, dev, func)) 107 | 108 | # construct AER sysfs filepath 109 | correctable_path = os.path.join(dev_path, "aer_dev_correctable") 110 | fatal_path = os.path.join(dev_path, "aer_dev_fatal") 111 | non_fatal_path = os.path.join(dev_path, "aer_dev_nonfatal") 112 | 113 | # update AER-correctable fields 114 | if os.path.isfile(correctable_path): 115 | with open(correctable_path, 'r') as fh: 116 | lines = fh.readlines() 117 | for line in lines: 118 | correctable_field, value = line.split() 119 | aer_stats['correctable'][correctable_field] = value 120 | 121 | # update AER-Fatal fields 122 | if os.path.isfile(fatal_path): 123 | with open(fatal_path, 'r') as fh: 124 | lines = fh.readlines() 125 | for line in lines: 126 | fatal_field, value = line.split() 127 | aer_stats['fatal'][fatal_field] = value 128 | 129 | # update AER-Non Fatal fields 130 | if os.path.isfile(non_fatal_path): 131 | with open(non_fatal_path, 'r') as fh: 132 | lines = fh.readlines() 133 | for line in lines: 134 | non_fatal_field, value = line.split() 135 | aer_stats['non_fatal'][non_fatal_field] = value 136 | 137 | return aer_stats 138 | 139 | # generate the config file with current pci device 140 | def dump_conf_yaml(self): 141 | curInfo = self.get_pcie_device() 142 | conf_rev = "_{}".format(self._conf_rev) if self._conf_rev else "" 143 | config_file = "{}/pcie{}.yaml".format(self.config_path, conf_rev) 144 | with open(config_file, "w") as conf_file: 145 | yaml.dump(curInfo, conf_file, default_flow_style=False) 146 | return 147 | -------------------------------------------------------------------------------- /sonic-pcied/tests/mocked_libs/swsscommon/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Mock implementation of swsscommon package for unit testing 3 | ''' 4 | 5 | from . import swsscommon 6 | -------------------------------------------------------------------------------- /sonic-pcied/tests/mocked_libs/swsscommon/swsscommon.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Mock implementation of swsscommon package for unit testing 3 | ''' 4 | 5 | STATE_DB = '' 6 | 7 | 8 | class Table: 9 | def __init__(self, db, table_name): 10 | self.table_name = table_name 11 | self.mock_dict = {} 12 | 13 | def _del(self, key): 14 | del self.mock_dict[key] 15 | pass 16 | 17 | def set(self, key, fvs): 18 | self.mock_dict[key] = fvs.fv_dict 19 | pass 20 | 21 | def get(self, key): 22 | if key in self.mock_dict: 23 | return self.mock_dict[key] 24 | return None 25 | 26 | def get_size(self): 27 | return (len(self.mock_dict)) 28 | 29 | def getKeys(self): 30 | return list(self.mock_dict.keys()) 31 | 32 | 33 | class FieldValuePairs: 34 | fv_dict = {} 35 | 36 | def __init__(self, tuple_list): 37 | if isinstance(tuple_list, list) and isinstance(tuple_list[0], tuple): 38 | self.fv_dict = dict(tuple_list) 39 | 40 | def __setitem__(self, key, kv_tuple): 41 | self.fv_dict[kv_tuple[0]] = kv_tuple[1] 42 | 43 | def __getitem__(self, key): 44 | return self.fv_dict[key] 45 | 46 | def __eq__(self, other): 47 | if not isinstance(other, FieldValuePairs): 48 | # don't attempt to compare against unrelated types 49 | return NotImplemented 50 | 51 | return self.fv_dict == other.fv_dict 52 | 53 | def __repr__(self): 54 | return repr(self.fv_dict) 55 | 56 | def __str__(self): 57 | return repr(self.fv_dict) 58 | 59 | class ConfigDBConnector: 60 | pass 61 | 62 | class SonicDBConfig: 63 | pass 64 | 65 | class SonicV2Connector: 66 | pass 67 | -------------------------------------------------------------------------------- /sonic-pcied/tests/test_pcied.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from imp import load_source # Replace with importlib once we no longer need to support Python 2 4 | 5 | import pytest 6 | 7 | # TODO: Clean this up once we no longer need to support Python 2 8 | if sys.version_info >= (3, 3): 9 | from unittest.mock import MagicMock, patch, mock_open 10 | else: 11 | from mock import MagicMock, patch, mock_open 12 | 13 | from .mock_platform import MockPcieUtil 14 | 15 | tests_path = os.path.dirname(os.path.abspath(__file__)) 16 | 17 | # Add mocked_libs path so that the file under test can load mocked modules from there 18 | mocked_libs_path = os.path.join(tests_path, "mocked_libs") 19 | sys.path.insert(0, mocked_libs_path) 20 | from sonic_py_common import daemon_base, device_info 21 | 22 | # Add path to the file under test so that we can load it 23 | modules_path = os.path.dirname(tests_path) 24 | scripts_path = os.path.join(modules_path, "scripts") 25 | sys.path.insert(0, modules_path) 26 | load_source('pcied', os.path.join(scripts_path, 'pcied')) 27 | import pcied 28 | 29 | 30 | daemon_base.db_connect = MagicMock() 31 | 32 | SYSLOG_IDENTIFIER = 'pcied_test' 33 | NOT_AVAILABLE = 'N/A' 34 | 35 | 36 | @patch('pcied.load_platform_pcieutil', MagicMock()) 37 | @patch('pcied.DaemonPcied.run') 38 | def test_main(mock_run): 39 | mock_run.return_value = False 40 | 41 | pcied.main() 42 | assert mock_run.call_count == 1 43 | 44 | 45 | @patch('pcied.os.path.exists', MagicMock(return_value=True)) 46 | def test_read_id_file(): 47 | 48 | device_name = "test" 49 | 50 | with patch('builtins.open', new_callable=mock_open, read_data='15') as mock_fd: 51 | rc = pcied.read_id_file(device_name) 52 | assert rc == "15" 53 | 54 | def test_load_platform_pcieutil(): 55 | with patch('pcied.log') as mock_log: 56 | # Case 1: Successfully get platform path 57 | with patch('pcied.device_info.get_paths_to_platform_and_hwsku_dirs', return_value=('/tmp', None)), \ 58 | patch('sonic_platform.pcie.Pcie') as mock_pcie: 59 | instance = mock_pcie.return_value 60 | result = pcied.load_platform_pcieutil() 61 | 62 | mock_pcie.assert_called_once_with('/tmp') 63 | assert result == instance 64 | mock_log.log_error.assert_not_called() 65 | 66 | # Case 2: Exception when getting platform path 67 | with patch('pcied.device_info.get_paths_to_platform_and_hwsku_dirs', side_effect=TypeError("Platform info not available")): 68 | result = pcied.load_platform_pcieutil() 69 | 70 | assert result is None 71 | mock_log.log_error.assert_called_once_with("Unable to get device info from Platform : TypeError : Platform info not available") 72 | 73 | 74 | # Cases 3-5: Test with /tmp as platform path 75 | with patch('pcied.device_info.get_paths_to_platform_and_hwsku_dirs', return_value=('/tmp', None)), \ 76 | patch('pcied.log') as mock_log: 77 | 78 | # Case 3: Successfully import sonic_platform.pcie.Pcie 79 | with patch('sonic_platform.pcie.Pcie') as mock_pcie: 80 | instance = mock_pcie.return_value 81 | result = pcied.load_platform_pcieutil() 82 | 83 | mock_pcie.assert_called_once_with('/tmp') 84 | assert result == instance 85 | mock_log.log_notice.assert_not_called() 86 | mock_log.log_error.assert_not_called() 87 | 88 | # Case 4: Fallback to sonic_platform_base.sonic_pcie.pcie_common.PcieUtil 89 | with patch('sonic_platform.pcie.Pcie', side_effect=ImportError("No module named 'sonic_platform.pcie'")), \ 90 | patch('sonic_platform_base.sonic_pcie.pcie_common.PcieUtil') as mock_pcieutil: 91 | instance = mock_pcieutil.return_value 92 | result = pcied.load_platform_pcieutil() 93 | 94 | mock_pcieutil.assert_called_once_with('/tmp') 95 | assert result == instance 96 | mock_log.log_notice.assert_called_once() 97 | mock_log.log_error.assert_not_called() 98 | mock_log.reset_mock() 99 | 100 | # Case 5: Failure to import both modules 101 | with patch('sonic_platform.pcie.Pcie', side_effect=ImportError("No module named 'sonic_platform.pcie'")), \ 102 | patch('sonic_platform_base.sonic_pcie.pcie_common.PcieUtil', side_effect=ImportError("No module named 'sonic_platform_base.sonic_pcie.pcie_common'")): 103 | with pytest.raises(RuntimeError, match="Unable to load PCIe utility module."): 104 | pcied.load_platform_pcieutil() 105 | 106 | mock_log.log_notice.assert_called_once() 107 | assert mock_log.log_error.call_count == 2 108 | -------------------------------------------------------------------------------- /sonic-psud/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml --junitxml=test-results.xml -vv 3 | -------------------------------------------------------------------------------- /sonic-psud/setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | -------------------------------------------------------------------------------- /sonic-psud/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='sonic-psud', 5 | version='1.0', 6 | description='PSU daemon for SONiC', 7 | license='Apache 2.0', 8 | author='SONiC Team', 9 | author_email='linuxnetdev@microsoft.com', 10 | url='https://github.com/Azure/sonic-platform-daemons', 11 | maintainer='Kevin Wang', 12 | maintainer_email='kevinw@mellanox.com', 13 | packages=[ 14 | 'tests' 15 | ], 16 | scripts=[ 17 | 'scripts/psud', 18 | ], 19 | setup_requires=[ 20 | 'pytest-runner', 21 | 'wheel' 22 | ], 23 | tests_require=[ 24 | 'mock>=2.0.0; python_version < "3.3"', 25 | 'pytest', 26 | 'pytest-cov', 27 | 'sonic_platform_common' 28 | ], 29 | classifiers=[ 30 | 'Development Status :: 4 - Beta', 31 | 'Environment :: No Input/Output (Daemon)', 32 | 'Intended Audience :: Developers', 33 | 'Intended Audience :: Information Technology', 34 | 'Intended Audience :: System Administrators', 35 | 'License :: OSI Approved :: Apache Software License', 36 | 'Natural Language :: English', 37 | 'Operating System :: POSIX :: Linux', 38 | 'Programming Language :: Python :: 2.7', 39 | 'Programming Language :: Python :: 3.7', 40 | 'Topic :: System :: Hardware', 41 | ], 42 | keywords='sonic SONiC psu PSU daemon psud PSUD', 43 | test_suite='setup.get_test_suite' 44 | ) 45 | -------------------------------------------------------------------------------- /sonic-psud/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-psud/tests/__init__.py -------------------------------------------------------------------------------- /sonic-psud/tests/mocked_libs/sonic_platform/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mock implementation of sonic_platform package for unit testing 3 | """ 4 | 5 | from . import psu 6 | -------------------------------------------------------------------------------- /sonic-psud/tests/mocked_libs/sonic_platform/psu.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mock implementation of sonic_platform package for unit testing 3 | """ 4 | 5 | from sonic_platform_base.psu_base import PsuBase 6 | 7 | 8 | class Psu(PsuBase): 9 | def __init__(self): 10 | super(PsuBase, self).__init__() 11 | -------------------------------------------------------------------------------- /sonic-psud/tests/mocked_libs/swsscommon/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Mock implementation of swsscommon package for unit testing 3 | ''' 4 | 5 | from . import swsscommon 6 | -------------------------------------------------------------------------------- /sonic-psud/tests/mocked_libs/swsscommon/swsscommon.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Mock implementation of swsscommon package for unit testing 3 | ''' 4 | 5 | from swsssdk import ConfigDBConnector, SonicDBConfig, SonicV2Connector 6 | 7 | STATE_DB = '' 8 | 9 | 10 | class Table: 11 | def __init__(self, db, table_name): 12 | self.table_name = table_name 13 | self.mock_dict = {} 14 | 15 | def _del(self, key): 16 | del self.mock_dict[key] 17 | pass 18 | 19 | def hdel(self, key, value): 20 | del self.mock_dict[key] 21 | pass 22 | 23 | def set(self, key, fvs): 24 | self.mock_dict[key] = fvs.fv_dict 25 | pass 26 | 27 | def get(self, key): 28 | if key in self.mock_dict: 29 | return self.mock_dict[key] 30 | return None 31 | 32 | def get_size(self): 33 | return (len(self.mock_dict)) 34 | 35 | 36 | class FieldValuePairs: 37 | fv_dict = {} 38 | 39 | def __init__(self, tuple_list): 40 | if isinstance(tuple_list, list) and isinstance(tuple_list[0], tuple): 41 | self.fv_dict = dict(tuple_list) 42 | 43 | def __setitem__(self, key, kv_tuple): 44 | self.fv_dict[kv_tuple[0]] = kv_tuple[1] 45 | 46 | def __getitem__(self, key): 47 | return self.fv_dict[key] 48 | 49 | def __eq__(self, other): 50 | if not isinstance(other, FieldValuePairs): 51 | # don't attempt to compare against unrelated types 52 | return NotImplemented 53 | 54 | return self.fv_dict == other.fv_dict 55 | 56 | def __repr__(self): 57 | return repr(self.fv_dict) 58 | 59 | def __str__(self): 60 | return repr(self.fv_dict) 61 | -------------------------------------------------------------------------------- /sonic-psud/tests/test_PsuStatus.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from imp import load_source # Replace with importlib once we no longer need to support Python 2 4 | 5 | # TODO: Clean this up once we no longer need to support Python 2 6 | if sys.version_info.major == 3: 7 | from unittest import mock 8 | else: 9 | import mock 10 | 11 | from .mock_platform import MockPsu 12 | 13 | tests_path = os.path.dirname(os.path.abspath(__file__)) 14 | 15 | # Add mocked_libs path so that the file under test can load mocked modules from there 16 | mocked_libs_path = os.path.join(tests_path, "mocked_libs") 17 | sys.path.insert(0, mocked_libs_path) 18 | 19 | # Add path to the file under test so that we can load it 20 | modules_path = os.path.dirname(tests_path) 21 | scripts_path = os.path.join(modules_path, "scripts") 22 | sys.path.insert(0, modules_path) 23 | load_source('psud', os.path.join(scripts_path, 'psud')) 24 | import psud 25 | 26 | 27 | class TestPsuStatus(object): 28 | """ 29 | Test cases to cover functionality of PsuStatus class 30 | """ 31 | 32 | def test_set_presence(self): 33 | mock_logger = mock.MagicMock() 34 | mock_psu = MockPsu("PSU 1", 0, True, True) 35 | 36 | psu_status = psud.PsuStatus(mock_logger, mock_psu, 1) 37 | assert psu_status.presence is True 38 | 39 | # Test toggling presence to False 40 | ret = psu_status.set_presence(False) 41 | assert ret == True 42 | assert psu_status.presence == False 43 | 44 | # Test toggling presence to True 45 | ret = psu_status.set_presence(True) 46 | assert ret == True 47 | assert psu_status.presence == True 48 | 49 | # Test attempting to set presence to the same as the current value 50 | ret = psu_status.set_presence(True) 51 | assert ret == False 52 | assert psu_status.presence == True 53 | 54 | def test_set_power_good(self): 55 | mock_logger = mock.MagicMock() 56 | mock_psu = MockPsu("PSU 1", 0, True, True) 57 | 58 | psu_status = psud.PsuStatus(mock_logger, mock_psu, 1) 59 | assert psu_status.power_good is True 60 | 61 | # Test toggling power_good to False 62 | ret = psu_status.set_power_good(False) 63 | assert ret == True 64 | assert psu_status.power_good == False 65 | 66 | # Test attempting to set power_good to the same as the current value (return value should be False) 67 | ret = psu_status.set_power_good(False) 68 | assert ret == False 69 | assert psu_status.power_good == False 70 | 71 | # Test toggling power_good to True 72 | ret = psu_status.set_power_good(True) 73 | assert ret == True 74 | assert psu_status.power_good == True 75 | 76 | # Test attempting to set power_good to the same as the current value (return value should be False) 77 | ret = psu_status.set_power_good(True) 78 | assert ret == False 79 | assert psu_status.power_good == True 80 | 81 | 82 | def test_set_voltage(self): 83 | mock_logger = mock.MagicMock() 84 | mock_psu = MockPsu("PSU 1", 0, True, True) 85 | 86 | psu_status = psud.PsuStatus(mock_logger, mock_psu, 1) 87 | assert psu_status.voltage_good is True 88 | 89 | # Pass in a high voltage 90 | ret = psu_status.set_voltage(12.6, 12.5, 11.5) 91 | assert ret == True 92 | assert psu_status.voltage_good == False 93 | 94 | # Pass in a another bad voltage successively (return value should be False) 95 | ret = psu_status.set_voltage(12.7, 12.5, 11.5) 96 | assert ret == False 97 | assert psu_status.voltage_good == False 98 | 99 | # Pass in a good (high edge case) voltage 100 | ret = psu_status.set_voltage(12.5, 12.5, 11.5) 101 | assert ret == True 102 | assert psu_status.voltage_good == True 103 | 104 | # Pass in a another good voltage successively (return value should be False) 105 | ret = psu_status.set_voltage(11.9, 12.5, 11.5) 106 | assert ret == False 107 | assert psu_status.voltage_good == True 108 | 109 | # Pass in a low voltage 110 | ret = psu_status.set_voltage(11.4, 12.5, 11.5) 111 | assert ret == True 112 | assert psu_status.voltage_good == False 113 | 114 | # Pass in a good (low edge case) voltage 115 | ret = psu_status.set_voltage(11.5, 12.5, 11.5) 116 | assert ret == True 117 | assert psu_status.voltage_good == True 118 | 119 | # Test passing parameters as None when voltage_good == True 120 | ret = psu_status.set_voltage(psud.NOT_AVAILABLE, 12.5, 11.5) 121 | assert ret == False 122 | assert psu_status.voltage_good == True 123 | ret = psu_status.set_voltage(11.5, psud.NOT_AVAILABLE, 11.5) 124 | assert ret == False 125 | assert psu_status.voltage_good == True 126 | ret = psu_status.set_voltage(11.5, 12.5, psud.NOT_AVAILABLE) 127 | assert ret == False 128 | assert psu_status.voltage_good == True 129 | 130 | # Test passing parameters as None when voltage_good == False 131 | psu_status.voltage_good = False 132 | ret = psu_status.set_voltage(psud.NOT_AVAILABLE, 12.5, 11.5) 133 | assert ret == False 134 | assert psu_status.voltage_good == True 135 | psu_status.voltage_good = False 136 | ret = psu_status.set_voltage(11.5, psud.NOT_AVAILABLE, 11.5) 137 | assert ret == False 138 | assert psu_status.voltage_good == True 139 | psu_status.voltage_good = False 140 | ret = psu_status.set_voltage(11.5, 12.5, psud.NOT_AVAILABLE) 141 | assert ret == False 142 | assert psu_status.voltage_good == True 143 | 144 | def test_set_temperature(self): 145 | mock_logger = mock.MagicMock() 146 | mock_psu = MockPsu("PSU 1", 0, True, True) 147 | 148 | psu_status = psud.PsuStatus(mock_logger, mock_psu, 1) 149 | assert psu_status.temperature_good is True 150 | 151 | # Pass in a high temperature 152 | ret = psu_status.set_temperature(50.001, 50.0) 153 | assert ret == True 154 | assert psu_status.temperature_good == False 155 | 156 | # Pass in a another bad temperature successively (return value should be False) 157 | ret = psu_status.set_temperature(50.0, 50.0) 158 | assert ret == False 159 | assert psu_status.temperature_good == False 160 | 161 | # Pass in a good (high edge case) temperature 162 | ret = psu_status.set_temperature(49.999, 50.0) 163 | assert ret == True 164 | assert psu_status.temperature_good == True 165 | 166 | # Pass in a another good temperature successively (return value should be False) 167 | ret = psu_status.set_temperature(31.456, 50.0) 168 | assert ret == False 169 | assert psu_status.temperature_good == True 170 | 171 | # Test passing parameters as None when temperature_good == True 172 | ret = psu_status.set_temperature(psud.NOT_AVAILABLE, 50.0) 173 | assert ret == False 174 | assert psu_status.temperature_good == True 175 | ret = psu_status.set_temperature(20.123, psud.NOT_AVAILABLE) 176 | assert ret == False 177 | assert psu_status.temperature_good == True 178 | 179 | # Test passing parameters as None when temperature_good == False 180 | psu_status.temperature_good = False 181 | ret = psu_status.set_temperature(psud.NOT_AVAILABLE, 50.0) 182 | assert ret == False 183 | assert psu_status.temperature_good == True 184 | psu_status.temperature_good = False 185 | ret = psu_status.set_temperature(20.123, psud.NOT_AVAILABLE) 186 | assert ret == False 187 | assert psu_status.temperature_good == True 188 | 189 | def test_is_ok(self): 190 | mock_logger = mock.MagicMock() 191 | mock_psu = MockPsu("PSU 1", 0, True, True) 192 | 193 | psu_status = psud.PsuStatus(mock_logger, mock_psu, 1) 194 | ret = psu_status.is_ok() 195 | assert ret == True 196 | 197 | psu_status.presence = False 198 | ret = psu_status.is_ok() 199 | assert ret == False 200 | 201 | psu_status.presence = True 202 | ret = psu_status.is_ok() 203 | assert ret == True 204 | 205 | psu_status.power_good = False 206 | ret = psu_status.is_ok() 207 | assert ret == False 208 | 209 | psu_status.power_good = True 210 | ret = psu_status.is_ok() 211 | assert ret == True 212 | 213 | psu_status.voltage_good = False 214 | ret = psu_status.is_ok() 215 | assert ret == False 216 | 217 | psu_status.voltage_good = True 218 | ret = psu_status.is_ok() 219 | assert ret == True 220 | 221 | psu_status.temperature_good = False 222 | ret = psu_status.is_ok() 223 | assert ret == False 224 | 225 | psu_status.temperature_good = True 226 | ret = psu_status.is_ok() 227 | assert ret == True 228 | -------------------------------------------------------------------------------- /sonic-psud/tests/test_psud.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from imp import load_source # Replace with importlib once we no longer need to support Python 2 4 | 5 | import pytest 6 | 7 | # TODO: Clean this up once we no longer need to support Python 2 8 | if sys.version_info.major == 3: 9 | from unittest import mock 10 | else: 11 | import mock 12 | from sonic_py_common import daemon_base 13 | 14 | from .mock_platform import MockPsu, MockChassis 15 | 16 | tests_path = os.path.dirname(os.path.abspath(__file__)) 17 | 18 | # Add mocked_libs path so that the file under test can load mocked modules from there 19 | mocked_libs_path = os.path.join(tests_path, "mocked_libs") 20 | sys.path.insert(0, mocked_libs_path) 21 | 22 | # Add path to the file under test so that we can load it 23 | modules_path = os.path.dirname(tests_path) 24 | scripts_path = os.path.join(modules_path, "scripts") 25 | sys.path.insert(0, modules_path) 26 | load_source('psud', os.path.join(scripts_path, 'psud')) 27 | import psud 28 | 29 | 30 | daemon_base.db_connect = mock.MagicMock() 31 | 32 | 33 | SYSLOG_IDENTIFIER = 'psud_test' 34 | NOT_AVAILABLE = 'N/A' 35 | 36 | 37 | @mock.patch('psud.platform_chassis', mock.MagicMock()) 38 | @mock.patch('psud.platform_psuutil', mock.MagicMock()) 39 | def test_wrapper_get_num_psus(): 40 | # Test new platform API is available and implemented 41 | psud._wrapper_get_num_psus() 42 | assert psud.platform_chassis.get_num_psus.call_count == 1 43 | assert psud.platform_psuutil.get_num_psus.call_count == 0 44 | 45 | # Test new platform API is available but not implemented 46 | psud.platform_chassis.get_num_psus.side_effect = NotImplementedError 47 | psud._wrapper_get_num_psus() 48 | assert psud.platform_chassis.get_num_psus.call_count == 2 49 | assert psud.platform_psuutil.get_num_psus.call_count == 1 50 | 51 | # Test new platform API not available 52 | psud.platform_chassis = None 53 | psud._wrapper_get_num_psus() 54 | assert psud.platform_psuutil.get_num_psus.call_count == 2 55 | 56 | 57 | @mock.patch('psud.platform_chassis', mock.MagicMock()) 58 | @mock.patch('psud.platform_psuutil', mock.MagicMock()) 59 | def test_wrapper_get_psu_presence(): 60 | # Test new platform API is available 61 | psud._wrapper_get_psu_presence(1) 62 | assert psud.platform_chassis.get_psu(0).get_presence.call_count == 1 63 | assert psud.platform_psuutil.get_psu_presence.call_count == 0 64 | 65 | # Test new platform API is available but not implemented 66 | psud.platform_chassis.get_psu(0).get_presence.side_effect = NotImplementedError 67 | psud._wrapper_get_psu_presence(1) 68 | assert psud.platform_chassis.get_psu(0).get_presence.call_count == 2 69 | assert psud.platform_psuutil.get_psu_presence.call_count == 1 70 | 71 | # Test new platform API not available 72 | psud.platform_chassis = None 73 | psud._wrapper_get_psu_presence(1) 74 | assert psud.platform_psuutil.get_psu_presence.call_count == 2 75 | psud.platform_psuutil.get_psu_presence.assert_called_with(1) 76 | 77 | 78 | @mock.patch('psud.platform_chassis', mock.MagicMock()) 79 | @mock.patch('psud.platform_psuutil', mock.MagicMock()) 80 | def test_wrapper_get_psu_status(): 81 | # Test new platform API is available 82 | psud._wrapper_get_psu_status(1) 83 | assert psud.platform_chassis.get_psu(0).get_powergood_status.call_count == 1 84 | assert psud.platform_psuutil.get_psu_status.call_count == 0 85 | 86 | # Test new platform API is available but not implemented 87 | psud.platform_chassis.get_psu(0).get_powergood_status.side_effect = NotImplementedError 88 | psud._wrapper_get_psu_status(1) 89 | assert psud.platform_chassis.get_psu(0).get_powergood_status.call_count == 2 90 | assert psud.platform_psuutil.get_psu_status.call_count == 1 91 | 92 | # Test new platform API not available 93 | psud.platform_chassis = None 94 | psud._wrapper_get_psu_status(1) 95 | assert psud.platform_psuutil.get_psu_status.call_count == 2 96 | psud.platform_psuutil.get_psu_status.assert_called_with(1) 97 | 98 | def test_log_on_status_changed(): 99 | normal_log = "Normal log message" 100 | abnormal_log = "Abnormal log message" 101 | 102 | mock_logger = mock.MagicMock() 103 | 104 | psud.log_on_status_changed(mock_logger, True, normal_log, abnormal_log) 105 | assert mock_logger.log_notice.call_count == 1 106 | assert mock_logger.log_warning.call_count == 0 107 | mock_logger.log_notice.assert_called_with(normal_log) 108 | 109 | mock_logger.log_notice.reset_mock() 110 | 111 | psud.log_on_status_changed(mock_logger, False, normal_log, abnormal_log) 112 | assert mock_logger.log_notice.call_count == 0 113 | assert mock_logger.log_warning.call_count == 1 114 | mock_logger.log_warning.assert_called_with(abnormal_log) 115 | 116 | 117 | @mock.patch('psud.platform_chassis', mock.MagicMock()) 118 | @mock.patch('psud.DaemonPsud.run') 119 | def test_main(mock_run): 120 | mock_run.return_value = False 121 | 122 | psud.main() 123 | assert mock_run.call_count == 1 124 | -------------------------------------------------------------------------------- /sonic-sensormond/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml --junitxml=test-results.xml -vv 3 | -------------------------------------------------------------------------------- /sonic-sensormond/setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | -------------------------------------------------------------------------------- /sonic-sensormond/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='sonic-sensormond', 5 | version='1.0', 6 | description='Sensor Monitor daemon for SONiC', 7 | license='Apache 2.0', 8 | author='SONiC Team', 9 | author_email='linuxnetdev@microsoft.com', 10 | url='https://github.com/Azure/sonic-platform-daemons', 11 | maintainer='Mridul Bajpai', 12 | maintainer_email='mridul@cisco.com', 13 | packages=[ 14 | 'tests' 15 | ], 16 | scripts=[ 17 | 'scripts/sensormond', 18 | ], 19 | setup_requires=[ 20 | 'pytest-runner', 21 | 'wheel' 22 | ], 23 | tests_require=[ 24 | 'mock>=2.0.0; python_version < "3.3"', 25 | 'pytest', 26 | 'pytest-cov', 27 | 'sonic-platform-common' 28 | ], 29 | classifiers=[ 30 | 'Development Status :: 4 - Beta', 31 | 'Intended Audience :: Developers', 32 | 'Intended Audience :: Information Technology', 33 | 'Intended Audience :: System Administrators', 34 | 'License :: OSI Approved :: Apache Software License', 35 | 'Natural Language :: English', 36 | 'Operating System :: POSIX :: Linux', 37 | 'Programming Language :: Python :: 2.7', 38 | 'Programming Language :: Python :: 3.7', 39 | 'Topic :: System :: Hardware', 40 | ], 41 | keywords='sonic SONiC SENSORMONITOR sensormonitor SENSORMON sensormon sensormond', 42 | test_suite='setup.get_test_suite' 43 | ) 44 | -------------------------------------------------------------------------------- /sonic-sensormond/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-sensormond/tests/__init__.py -------------------------------------------------------------------------------- /sonic-sensormond/tests/mock_platform.py: -------------------------------------------------------------------------------- 1 | from sonic_platform_base import chassis_base 2 | from sonic_platform_base import module_base 3 | 4 | class MockVoltageSensor(): 5 | def __init__(self, index=None): 6 | self._name = 'Voltage sensor {}'.format(index) if index != None else None 7 | self._presence = True 8 | self._model = 'Voltage sensor model' 9 | self._serial = 'Voltage sensor serial' 10 | self._status = True 11 | self._position_in_parent = 1 12 | self._replaceable = False 13 | 14 | self._value = 2 15 | self._minimum_value = 1 16 | self._maximum_value = 5 17 | self._high_threshold = 3 18 | self._low_threshold = 1 19 | self._high_critical_threshold = 4 20 | self._low_critical_threshold = 0 21 | 22 | def get_value(self): 23 | return self._value 24 | 25 | def get_unit(self): 26 | return "mV" 27 | 28 | def get_minimum_recorded(self): 29 | return self._minimum_value 30 | 31 | def get_maximum_recorded(self): 32 | return self._maximum_value 33 | 34 | def get_high_threshold(self): 35 | return self._high_threshold 36 | 37 | def get_low_threshold(self): 38 | return self._low_threshold 39 | 40 | def get_high_critical_threshold(self): 41 | return self._high_critical_threshold 42 | 43 | def get_low_critical_threshold(self): 44 | return self._low_critical_threshold 45 | 46 | def make_over_threshold(self): 47 | self._high_threshold = 2 48 | self._value = 3 49 | self._low_threshold = 1 50 | 51 | def make_under_threshold(self): 52 | self._high_threshold = 3 53 | self._value = 1 54 | self._low_threshold = 2 55 | 56 | def make_normal_value(self): 57 | self._high_threshold = 3 58 | self._value = 2 59 | self._low_threshold = 1 60 | 61 | # Methods inherited from DeviceBase class and related setters 62 | def get_name(self): 63 | return self._name 64 | 65 | def get_presence(self): 66 | return self._presence 67 | 68 | def set_presence(self, presence): 69 | self._presence = presence 70 | 71 | def get_model(self): 72 | return self._model 73 | 74 | def get_serial(self): 75 | return self._serial 76 | 77 | def get_status(self): 78 | return self._status 79 | 80 | def set_status(self, status): 81 | self._status = status 82 | 83 | def get_position_in_parent(self): 84 | return self._position_in_parent 85 | 86 | def is_replaceable(self): 87 | return self._replaceable 88 | 89 | class MockCurrentSensor(): 90 | def __init__(self, index=None): 91 | self._name = 'Current sensor {}'.format(index) if index != None else None 92 | self._presence = True 93 | self._model = 'Current sensor model' 94 | self._serial = 'Current sensor serial' 95 | self._status = True 96 | self._position_in_parent = 1 97 | self._replaceable = False 98 | 99 | self._value = 2 100 | self._minimum_value = 1 101 | self._maximum_value = 5 102 | self._high_threshold = 3 103 | self._low_threshold = 1 104 | self._high_critical_threshold = 4 105 | self._low_critical_threshold = 0 106 | 107 | def get_value(self): 108 | return self._value 109 | 110 | def get_unit(self): 111 | return "mA" 112 | 113 | def get_minimum_recorded(self): 114 | return self._minimum_value 115 | 116 | def get_maximum_recorded(self): 117 | return self._maximum_value 118 | 119 | def get_high_threshold(self): 120 | return self._high_threshold 121 | 122 | def get_low_threshold(self): 123 | return self._low_threshold 124 | 125 | def get_high_critical_threshold(self): 126 | return self._high_critical_threshold 127 | 128 | def get_low_critical_threshold(self): 129 | return self._low_critical_threshold 130 | 131 | def make_over_threshold(self): 132 | self._high_threshold = 2 133 | self._value = 3 134 | self._low_threshold = 1 135 | 136 | def make_under_threshold(self): 137 | self._high_threshold = 3 138 | self._value = 1 139 | self._low_threshold = 2 140 | 141 | def make_normal_value(self): 142 | self._high_threshold = 3 143 | self._value = 2 144 | self._low_threshold = 1 145 | 146 | # Methods inherited from DeviceBase class and related setters 147 | def get_name(self): 148 | return self._name 149 | 150 | def get_presence(self): 151 | return self._presence 152 | 153 | def set_presence(self, presence): 154 | self._presence = presence 155 | 156 | def get_model(self): 157 | return self._model 158 | 159 | def get_serial(self): 160 | return self._serial 161 | 162 | def get_status(self): 163 | return self._status 164 | 165 | def set_status(self, status): 166 | self._status = status 167 | 168 | def get_position_in_parent(self): 169 | return self._position_in_parent 170 | 171 | def is_replaceable(self): 172 | return self._replaceable 173 | 174 | class MockErrorVoltageSensor(MockVoltageSensor): 175 | def get_value(self): 176 | raise Exception('Failed to get voltage') 177 | 178 | class MockErrorCurrentSensor(MockCurrentSensor): 179 | def get_value(self): 180 | raise Exception('Failed to get current') 181 | 182 | class MockChassis(chassis_base.ChassisBase): 183 | def __init__(self): 184 | super(MockChassis, self).__init__() 185 | self._name = None 186 | self._presence = True 187 | self._model = 'Chassis Model' 188 | self._serial = 'Chassis Serial' 189 | self._status = True 190 | self._position_in_parent = 1 191 | self._replaceable = False 192 | self._current_sensor_list = [] 193 | self._voltage_sensor_list = [] 194 | 195 | self._is_chassis_system = False 196 | self._my_slot = module_base.ModuleBase.MODULE_INVALID_SLOT 197 | 198 | def get_num_voltage_sensors(self): 199 | return len(self._voltage_sensor_list) 200 | 201 | def get_num_current_sensors(self): 202 | return len(self._current_sensor_list) 203 | 204 | def get_all_voltage_sensors(self): 205 | return self._voltage_sensor_list 206 | 207 | def get_all_current_sensors(self): 208 | return self._current_sensor_list 209 | 210 | def make_over_threshold_voltage_sensor(self): 211 | voltage_sensor = MockVoltageSensor() 212 | voltage_sensor.make_over_threshold() 213 | self._voltage_sensor_list.append(voltage_sensor) 214 | 215 | def make_under_threshold_voltage_sensor(self): 216 | voltage_sensor = MockVoltageSensor() 217 | voltage_sensor.make_under_threshold() 218 | self._voltage_sensor_list.append(voltage_sensor) 219 | 220 | def make_error_voltage_sensor(self): 221 | voltage_sensor = MockErrorVoltageSensor() 222 | self._voltage_sensor_list.append(voltage_sensor) 223 | 224 | def make_module_voltage_sensor(self): 225 | module = MockModule() 226 | self._module_list.append(module) 227 | module._voltage_sensor_list.append(MockVoltageSensor()) 228 | 229 | def make_over_threshold_current_sensor(self): 230 | current_sensor = MockCurrentSensor() 231 | current_sensor.make_over_threshold() 232 | self._current_sensor_list.append(current_sensor) 233 | 234 | def make_under_threshold_current_sensor(self): 235 | current_sensor = MockCurrentSensor() 236 | current_sensor.make_under_threshold() 237 | self._current_sensor_list.append(current_sensor) 238 | 239 | def make_error_current_sensor(self): 240 | current_sensor = MockErrorCurrentSensor() 241 | self._current_sensor_list.append(current_sensor) 242 | 243 | def make_module_current_sensor(self): 244 | module = MockModule() 245 | self._module_list.append(module) 246 | module._current_sensor_list.append(MockCurrentSensor()) 247 | 248 | def is_modular_chassis(self): 249 | return self._is_chassis_system 250 | 251 | def set_modular_chassis(self, is_true): 252 | self._is_chassis_system = is_true 253 | 254 | def set_my_slot(self, my_slot): 255 | self._my_slot = my_slot 256 | 257 | def get_my_slot(self): 258 | return self._my_slot 259 | 260 | # Methods inherited from DeviceBase class and related setters 261 | def get_name(self): 262 | return self._name 263 | 264 | def get_presence(self): 265 | return self._presence 266 | 267 | def set_presence(self, presence): 268 | self._presence = presence 269 | 270 | def get_model(self): 271 | return self._model 272 | 273 | def get_serial(self): 274 | return self._serial 275 | 276 | def get_status(self): 277 | return self._status 278 | 279 | def set_status(self, status): 280 | self._status = status 281 | 282 | def get_position_in_parent(self): 283 | return self._position_in_parent 284 | 285 | def is_replaceable(self): 286 | return self._replaceable 287 | 288 | 289 | class MockModule(module_base.ModuleBase): 290 | def __init__(self): 291 | super(MockModule, self).__init__() 292 | self._current_sensor_list = [] 293 | self._voltage_sensor_list = [] 294 | 295 | def get_all_voltage_sensors(self): 296 | return self._voltage_sensor_list 297 | 298 | def get_all_current_sensors(self): 299 | return self._current_sensor_list 300 | 301 | -------------------------------------------------------------------------------- /sonic-sensormond/tests/mock_swsscommon.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Mock implementation of swsscommon package for unit testing 3 | ''' 4 | 5 | from swsssdk import ConfigDBConnector, SonicDBConfig, SonicV2Connector 6 | 7 | STATE_DB = '' 8 | 9 | 10 | class Table: 11 | def __init__(self, db, table_name): 12 | self.table_name = table_name 13 | self.mock_dict = {} 14 | self.mock_keys = [] 15 | 16 | def _del(self, key): 17 | del self.mock_dict[key] 18 | pass 19 | 20 | def set(self, key, fvs): 21 | self.mock_dict[key] = fvs.fv_dict 22 | pass 23 | 24 | def get(self, key): 25 | if key in self.mock_dict: 26 | return self.mock_dict[key] 27 | return None 28 | 29 | def get_size(self): 30 | return (len(self.mock_dict)) 31 | 32 | def getKeys(self): 33 | return self.mock_keys 34 | 35 | 36 | class FieldValuePairs: 37 | fv_dict = {} 38 | 39 | def __init__(self, tuple_list): 40 | if isinstance(tuple_list, list) and isinstance(tuple_list[0], tuple): 41 | self.fv_dict = dict(tuple_list) 42 | 43 | def __setitem__(self, key, kv_tuple): 44 | self.fv_dict[kv_tuple[0]] = kv_tuple[1] 45 | 46 | def __getitem__(self, key): 47 | return self.fv_dict[key] 48 | 49 | def __eq__(self, other): 50 | if not isinstance(other, FieldValuePairs): 51 | # don't attempt to compare against unrelated types 52 | return NotImplemented 53 | 54 | return self.fv_dict == other.fv_dict 55 | 56 | def __repr__(self): 57 | return repr(self.fv_dict) 58 | 59 | def __str__(self): 60 | return repr(self.fv_dict) 61 | -------------------------------------------------------------------------------- /sonic-sensormond/tests/mocked_libs/sonic_platform/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mock implementation of sonic_platform package for unit testing 3 | """ 4 | 5 | from . import chassis 6 | from . import platform 7 | -------------------------------------------------------------------------------- /sonic-sensormond/tests/mocked_libs/sonic_platform/chassis.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mock implementation of sonic_platform package for unit testing 3 | """ 4 | 5 | import sys 6 | from unittest import mock 7 | from sonic_platform_base.chassis_base import ChassisBase 8 | 9 | 10 | class Chassis(ChassisBase): 11 | def __init__(self): 12 | ChassisBase.__init__(self) 13 | self._eeprom = mock.MagicMock() 14 | 15 | def get_eeprom(self): 16 | return self._eeprom 17 | -------------------------------------------------------------------------------- /sonic-sensormond/tests/mocked_libs/sonic_platform/platform.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mock implementation of sonic_platform package for unit testing 3 | """ 4 | 5 | from sonic_platform_base.platform_base import PlatformBase 6 | from sonic_platform.chassis import Chassis 7 | 8 | class Platform(PlatformBase): 9 | def __init__(self): 10 | PlatformBase.__init__(self) 11 | self._chassis = Chassis() 12 | -------------------------------------------------------------------------------- /sonic-sensormond/tests/sensors.yaml: -------------------------------------------------------------------------------- 1 | voltage_sensors: 2 | - name : VSENSOR1 3 | sensor: 'sensor_data/VSENSOR1' 4 | high_thresholds: [ 1000, 1050, 1080 ] 5 | low_thresholds: [ 800, 850, 890 ] 6 | - name : VSENSOR2 7 | sensor: 'sensor_data/VSENSOR2' 8 | high_thresholds: [ 800, 850, 870 ] 9 | low_thresholds: [ 600, 620, 750 ] 10 | current_sensors: 11 | - name : CSENSOR1 12 | sensor: 'sensor_data/CSENSOR1' 13 | high_thresholds: [ 1000, 1050, 1080 ] 14 | low_thresholds: [ 800, 850, 890 ] 15 | - name : CSENSOR2 16 | sensor: 'sensor_data/CSENSOR2' 17 | high_thresholds: [ 800, 850, 870 ] 18 | low_thresholds: [ 600, 620, 750 ] 19 | -------------------------------------------------------------------------------- /sonic-stormond/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml --junitxml=test-results.xml -vv 3 | -------------------------------------------------------------------------------- /sonic-stormond/setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | -------------------------------------------------------------------------------- /sonic-stormond/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='sonic-stormond', 5 | version='1.0', 6 | description='Storage Device Monitoring Daemon for SONiC', 7 | license='Apache 2.0', 8 | author='SONiC Team', 9 | author_email='linuxnetdev@microsoft.com', 10 | url='https://github.com/sonic-net/sonic-platform-daemons', 11 | maintainer='Ashwin Srinivasan', 12 | maintainer_email='assrinivasan@microsoft.com', 13 | scripts=[ 14 | 'scripts/stormond', 15 | ], 16 | setup_requires=[ 17 | 'pytest-runner', 18 | 'wheel' 19 | ], 20 | install_requires=[ 21 | 'enum34', 22 | 'sonic-py-common', 23 | ], 24 | tests_require=[ 25 | 'mock>=2.0.0', 26 | 'pytest', 27 | 'pytest-cov', 28 | ], 29 | classifiers=[ 30 | 'Development Status :: 4 - Beta', 31 | 'Environment :: No Input/Output (Daemon)', 32 | 'Intended Audience :: Developers', 33 | 'Intended Audience :: Information Technology', 34 | 'Intended Audience :: System Administrators', 35 | 'License :: OSI Approved :: Apache Software License', 36 | 'Natural Language :: English', 37 | 'Operating System :: POSIX :: Linux', 38 | 'Topic :: System :: Hardware', 39 | ], 40 | keywords='sonic SONiC ssd Ssd SSD ssdmond storage stormond storagemond', 41 | test_suite='setup.get_test_suite' 42 | ) 43 | -------------------------------------------------------------------------------- /sonic-stormond/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-stormond/tests/__init__.py -------------------------------------------------------------------------------- /sonic-stormond/tests/mock_swsscommon.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Mock implementation of swsscommon package for unit testing 3 | ''' 4 | 5 | from swsssdk import ConfigDBConnector, SonicDBConfig, SonicV2Connector 6 | 7 | STATE_DB = '' 8 | 9 | 10 | class Table: 11 | def __init__(self, db, table_name): 12 | self.table_name = table_name 13 | self.mock_dict = {} 14 | self.mock_keys = ['sda'] 15 | 16 | def _del(self, key): 17 | del self.mock_dict[key] 18 | pass 19 | 20 | def set(self, key, fvs): 21 | self.mock_dict[key] = fvs.fv_dict 22 | pass 23 | 24 | def get(self, key): 25 | if key in self.mock_dict: 26 | return self.mock_dict[key] 27 | return None 28 | 29 | def get_size(self): 30 | return (len(self.mock_dict)) 31 | 32 | def getKeys(self): 33 | return self.mock_keys 34 | 35 | def hgetall(self): 36 | return self.mock_dict 37 | 38 | 39 | class FieldValuePairs: 40 | fv_dict = {} 41 | 42 | def __init__(self, tuple_list): 43 | if isinstance(tuple_list, list) and isinstance(tuple_list[0], tuple): 44 | self.fv_dict = dict(tuple_list) 45 | 46 | def __setitem__(self, key, kv_tuple): 47 | self.fv_dict[kv_tuple[0]] = kv_tuple[1] 48 | 49 | def __getitem__(self, key): 50 | return self.fv_dict[key] 51 | 52 | def __eq__(self, other): 53 | if not isinstance(other, FieldValuePairs): 54 | # don't attempt to compare against unrelated types 55 | return NotImplemented 56 | 57 | return self.fv_dict == other.fv_dict 58 | 59 | def __repr__(self): 60 | return repr(self.fv_dict) 61 | 62 | def __str__(self): 63 | return repr(self.fv_dict) 64 | -------------------------------------------------------------------------------- /sonic-stormond/tests/mocked_libs/sonic_platform/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mock implementation of sonic_platform package for unit testing 3 | """ 4 | 5 | from . import pcie 6 | 7 | -------------------------------------------------------------------------------- /sonic-stormond/tests/mocked_libs/sonic_platform/pcie.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mock implementation of sonic_platform package for unit testing 3 | """ 4 | 5 | from sonic_platform_base.pcie_base import PcieBase 6 | 7 | 8 | class Pcie(PcieBase): 9 | def __init__(self): 10 | self.platform_pcieutil = "/tmp/Pcie" 11 | 12 | def __str__(self): 13 | return self.platform_pcieutil 14 | -------------------------------------------------------------------------------- /sonic-stormond/tests/mocked_libs/sonic_platform_base/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/__init__.py -------------------------------------------------------------------------------- /sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_base.py: -------------------------------------------------------------------------------- 1 | # 2 | # storage_base.py 3 | # 4 | # Base class for implementing common SSD health features 5 | # 6 | 7 | 8 | class StorageBase(object): 9 | """ 10 | Base class for interfacing with a SSD 11 | """ 12 | def __init__(self, diskdev): 13 | """ 14 | Constructor 15 | 16 | Args: 17 | diskdev: Linux device name to get parameters for 18 | """ 19 | pass 20 | 21 | def get_health(self): 22 | """ 23 | Retrieves current disk health in percentages 24 | 25 | Returns: 26 | A float number of current ssd health 27 | e.g. 83.5 28 | """ 29 | raise NotImplementedError 30 | 31 | def get_temperature(self): 32 | """ 33 | Retrieves current disk temperature in Celsius 34 | 35 | Returns: 36 | A float number of current temperature in Celsius 37 | e.g. 40.1 38 | """ 39 | raise NotImplementedError 40 | 41 | def get_model(self): 42 | """ 43 | Retrieves model for the given disk device 44 | 45 | Returns: 46 | A string holding disk model as provided by the manufacturer 47 | """ 48 | raise NotImplementedError 49 | 50 | def get_firmware(self): 51 | """ 52 | Retrieves firmware version for the given disk device 53 | 54 | Returns: 55 | A string holding disk firmware version as provided by the manufacturer 56 | """ 57 | raise NotImplementedError 58 | 59 | def get_serial(self): 60 | """ 61 | Retrieves serial number for the given disk device 62 | 63 | Returns: 64 | A string holding disk serial number as provided by the manufacturer 65 | """ 66 | raise NotImplementedError 67 | 68 | def get_vendor_output(self): 69 | """ 70 | Retrieves vendor specific data for the given disk device 71 | 72 | Returns: 73 | A string holding some vendor specific disk information 74 | """ 75 | raise NotImplementedError 76 | 77 | def get_disk_io_reads(self): 78 | """ 79 | Retrieves the total number of Input/Output (I/O) reads done on a storage disk 80 | 81 | Returns: 82 | An integer value of the total number of I/O reads 83 | """ 84 | raise NotImplementedError 85 | 86 | def get_disk_io_writes(self): 87 | """ 88 | Retrieves the total number of Input/Output (I/O) writes done on a storage disk 89 | 90 | Returns: 91 | An integer value of the total number of I/O writes 92 | """ 93 | raise NotImplementedError 94 | 95 | def get_reserved_blocks(self): 96 | """ 97 | Retrieves the total number of reserved blocks in an storage disk 98 | 99 | Returns: 100 | An integer value of the total number of reserved blocks 101 | """ 102 | raise NotImplementedError 103 | -------------------------------------------------------------------------------- /sonic-stormond/tests/mocked_libs/sonic_platform_base/sonic_storage/storage_devices.py: -------------------------------------------------------------------------------- 1 | 2 | BLKDEV_BASE_PATH = '' 3 | 4 | class StorageDevices: 5 | def __init__(self): 6 | self.devices = {'sda' : None} 7 | 8 | def _get_storage_devices(self): 9 | pass 10 | 11 | def _storage_device_object_factory(self, key): 12 | pass -------------------------------------------------------------------------------- /sonic-stormond/tests/mocked_libs/swsscommon/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Mock implementation of swsscommon package for unit testing 3 | ''' 4 | 5 | from . import swsscommon 6 | -------------------------------------------------------------------------------- /sonic-stormond/tests/mocked_libs/swsscommon/swsscommon.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Mock implementation of swsscommon package for unit testing 3 | ''' 4 | 5 | STATE_DB = '' 6 | 7 | 8 | class Table: 9 | def __init__(self, db, table_name): 10 | self.table_name = table_name 11 | self.mock_dict = {} 12 | 13 | def _del(self, key): 14 | del self.mock_dict[key] 15 | pass 16 | 17 | def set(self, key, fvs): 18 | self.mock_dict[key] = fvs.fv_dict 19 | pass 20 | 21 | def get(self, key): 22 | if key in self.mock_dict: 23 | return self.mock_dict[key] 24 | return None 25 | 26 | def get_size(self): 27 | return (len(self.mock_dict)) 28 | 29 | def getKeys(self): 30 | return list(self.mock_dict.keys()) 31 | 32 | 33 | class FieldValuePairs: 34 | fv_dict = {} 35 | 36 | def __init__(self, tuple_list): 37 | if isinstance(tuple_list, list) and isinstance(tuple_list[0], tuple): 38 | self.fv_dict = dict(tuple_list) 39 | 40 | def __setitem__(self, key, kv_tuple): 41 | self.fv_dict[kv_tuple[0]] = kv_tuple[1] 42 | 43 | def __getitem__(self, key): 44 | return self.fv_dict[key] 45 | 46 | def __eq__(self, other): 47 | if not isinstance(other, FieldValuePairs): 48 | # don't attempt to compare against unrelated types 49 | return NotImplemented 50 | 51 | return self.fv_dict == other.fv_dict 52 | 53 | def __repr__(self): 54 | return repr(self.fv_dict) 55 | 56 | def __str__(self): 57 | return repr(self.fv_dict) 58 | 59 | class ConfigDBConnector: 60 | pass 61 | 62 | class SonicDBConfig: 63 | pass 64 | 65 | class SonicV2Connector: 66 | pass 67 | -------------------------------------------------------------------------------- /sonic-syseepromd/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml --junitxml=test-results.xml -vv 3 | -------------------------------------------------------------------------------- /sonic-syseepromd/scripts/syseepromd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | ''' 4 | syseepromd 5 | Syseeprom information gathering daemon for SONiC 6 | This daemon will be started during the start phase of pmon container, gathering syseeprom info and write to state DB. 7 | It will continue monitoring the state DB for the syseeprom table, if table was deleted, it will write again. 8 | With this daemon, show syseeprom CLI will be able to get data from state DB instead of access hw or cache. 9 | ''' 10 | 11 | import signal 12 | import sys 13 | import threading 14 | 15 | from sonic_py_common import daemon_base 16 | from swsscommon import swsscommon 17 | 18 | 19 | # TODO: Once we no longer support Python 2, we can eliminate this and get the 20 | # name using the 'name' field (e.g., `signal.SIGINT.name`) starting with Python 3.5 21 | SIGNALS_TO_NAMES_DICT = dict((getattr(signal, n), n) 22 | for n in dir(signal) if n.startswith('SIG') and '_' not in n) 23 | 24 | PLATFORM_SPECIFIC_MODULE_NAME = 'eeprom' 25 | PLATFORM_SPECIFIC_CLASS_NAME = 'board' 26 | 27 | EEPROM_INFO_UPDATE_PERIOD_SECS = 60 28 | 29 | ERR_NONE = 0 30 | ERR_PLATFORM_NOT_SUPPORT = 1 31 | ERR_FAILED_EEPROM = 2 32 | ERR_FAILED_UPDATE_DB = 3 33 | ERR_INVALID_PARAMETER = 4 34 | ERR_EEPROM_LOAD = 5 35 | 36 | EEPROM_TABLE_NAME = 'EEPROM_INFO' 37 | 38 | SYSLOG_IDENTIFIER = 'syseepromd' 39 | 40 | exit_code = 0 41 | 42 | 43 | class DaemonSyseeprom(daemon_base.DaemonBase): 44 | def __init__(self): 45 | super(DaemonSyseeprom, self).__init__(SYSLOG_IDENTIFIER) 46 | 47 | # Set minimum logging level to INFO 48 | self.set_min_log_priority_info() 49 | 50 | self.stop_event = threading.Event() 51 | self.eeprom = None 52 | self.eeprom_tbl = None 53 | 54 | # First, try to load the new platform API 55 | try: 56 | import sonic_platform 57 | self.eeprom = sonic_platform.platform.Platform().get_chassis().get_eeprom() 58 | except Exception as e: 59 | self.log_warning( 60 | "Failed to load platform-specific eeprom from sonic_platform package due to {}. Trying deprecated plugin method ...".format(repr(e))) 61 | 62 | # If we didn't successfully load the class from the sonic_platform package, try loading the old plugin 63 | try: 64 | self.eeprom = self.load_platform_util(PLATFORM_SPECIFIC_MODULE_NAME, PLATFORM_SPECIFIC_CLASS_NAME) 65 | except Exception as e: 66 | self.log_error("Failed to load platform-specific eeprom from deprecated plugin: {}".format(repr(e))) 67 | 68 | if not self.eeprom: 69 | sys.exit(ERR_EEPROM_LOAD) 70 | 71 | # Connect to STATE_DB 72 | state_db = daemon_base.db_connect("STATE_DB") 73 | self.eeprom_tbl = swsscommon.Table(state_db, EEPROM_TABLE_NAME) 74 | self.eepromtbl_keys = [] 75 | 76 | # Post system EEPROM info to state DB once at start-up 77 | rc = self.post_eeprom_to_db() 78 | if rc != ERR_NONE: 79 | self.log_error("Failed to post system EEPROM info to database") 80 | 81 | def __del__(self): 82 | # Delete all the information from DB 83 | self.clear_db() 84 | 85 | def post_eeprom_to_db(self): 86 | eeprom_data = self.eeprom.read_eeprom() 87 | if eeprom_data is None: 88 | self.log_error("Failed to read EEPROM") 89 | return ERR_FAILED_EEPROM 90 | 91 | err = self.eeprom.update_eeprom_db(eeprom_data) 92 | if err: 93 | self.log_error("Failed to update EEPROM info in database") 94 | return ERR_FAILED_UPDATE_DB 95 | 96 | self.eepromtbl_keys = self.eeprom_tbl.getKeys() 97 | 98 | return ERR_NONE 99 | 100 | def clear_db(self): 101 | if self.eeprom_tbl: 102 | keys = self.eeprom_tbl.getKeys() 103 | for key in keys: 104 | self.eeprom_tbl._del(key) 105 | 106 | def detect_eeprom_table_integrity(self): 107 | keys = self.eeprom_tbl.getKeys() 108 | 109 | if len(keys) != len(self.eepromtbl_keys): 110 | return False 111 | 112 | for key in self.eepromtbl_keys: 113 | if key not in keys: 114 | return False 115 | 116 | return True 117 | 118 | # Override signal handler from DaemonBase 119 | def signal_handler(self, sig, frame): 120 | FATAL_SIGNALS = [signal.SIGINT, signal.SIGTERM] 121 | NONFATAL_SIGNALS = [signal.SIGHUP] 122 | 123 | global exit_code 124 | 125 | if sig in FATAL_SIGNALS: 126 | self.log_info("Caught signal '{}' - exiting...".format(SIGNALS_TO_NAMES_DICT[sig])) 127 | exit_code = 128 + sig # Make sure we exit with a non-zero code so that supervisor will try to restart us 128 | self.stop_event.set() 129 | elif sig in NONFATAL_SIGNALS: 130 | self.log_info("Caught signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) 131 | else: 132 | self.log_warning("Caught unhandled signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) 133 | 134 | # Main daemon logic 135 | def run(self): 136 | if self.stop_event.wait(EEPROM_INFO_UPDATE_PERIOD_SECS): 137 | # We received a fatal signal 138 | return False 139 | 140 | rc = self.detect_eeprom_table_integrity() 141 | if not rc: 142 | self.log_info("System EEPROM table was changed, needs update") 143 | self.clear_db() 144 | rcs = self.post_eeprom_to_db() 145 | if rcs != ERR_NONE: 146 | self.log_error("Failed to post EEPROM to database") 147 | 148 | return True 149 | 150 | # 151 | # Main ========================================================================= 152 | # 153 | 154 | 155 | def main(): 156 | syseepromd = DaemonSyseeprom() 157 | 158 | syseepromd.log_info("Starting up...") 159 | 160 | while syseepromd.run(): 161 | pass 162 | 163 | syseepromd.log_info("Shutting down...") 164 | 165 | return exit_code 166 | 167 | 168 | if __name__ == '__main__': 169 | sys.exit(main()) 170 | -------------------------------------------------------------------------------- /sonic-syseepromd/setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | -------------------------------------------------------------------------------- /sonic-syseepromd/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='sonic-syseepromd', 5 | version='1.0', 6 | description='Syseeprom gathering daemon for SONiC', 7 | license='Apache 2.0', 8 | author='SONiC Team', 9 | author_email='linuxnetdev@microsoft.com', 10 | url='https://github.com/Azure/sonic-platform-daemons', 11 | maintainer='Kebo Liu', 12 | maintainer_email='kebol@mellanox.com', 13 | scripts=[ 14 | 'scripts/syseepromd', 15 | ], 16 | setup_requires=[ 17 | 'wheel' 18 | ], 19 | tests_require=[ 20 | 'mock>=2.0.0; python_version < "3.3"', 21 | 'pytest', 22 | 'pytest-cov', 23 | 'sonic_platform_common' 24 | ], 25 | classifiers=[ 26 | 'Development Status :: 4 - Beta', 27 | 'Environment :: No Input/Output (Daemon)', 28 | 'Intended Audience :: Developers', 29 | 'Intended Audience :: Information Technology', 30 | 'Intended Audience :: System Administrators', 31 | 'License :: OSI Approved :: Apache Software License', 32 | 'Natural Language :: English', 33 | 'Operating System :: POSIX :: Linux', 34 | 'Programming Language :: Python :: 2.7', 35 | 'Programming Language :: Python :: 3.7', 36 | 'Topic :: System :: Hardware', 37 | ], 38 | keywords='sonic SONiC SYSEEPROM syseeprom SYSEEPROMD syseepromd', 39 | ) 40 | -------------------------------------------------------------------------------- /sonic-syseepromd/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-syseepromd/tests/__init__.py -------------------------------------------------------------------------------- /sonic-syseepromd/tests/mocked_libs/sonic_platform/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mock implementation of sonic_platform package for unit testing 3 | """ 4 | 5 | from . import chassis 6 | from . import platform 7 | -------------------------------------------------------------------------------- /sonic-syseepromd/tests/mocked_libs/sonic_platform/chassis.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mock implementation of sonic_platform package for unit testing 3 | """ 4 | 5 | # TODO: Clean this up once we no longer need to support Python 2 6 | import sys 7 | if sys.version_info.major == 3: 8 | from unittest import mock 9 | else: 10 | import mock 11 | 12 | from sonic_platform_base.chassis_base import ChassisBase 13 | 14 | 15 | class Chassis(ChassisBase): 16 | def __init__(self): 17 | ChassisBase.__init__(self) 18 | self.eeprom = mock.MagicMock() 19 | 20 | def get_eeprom(self): 21 | return self.eeprom 22 | -------------------------------------------------------------------------------- /sonic-syseepromd/tests/mocked_libs/sonic_platform/platform.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mock implementation of sonic_platform package for unit testing 3 | """ 4 | 5 | from sonic_platform_base.platform_base import PlatformBase 6 | from sonic_platform.chassis import Chassis 7 | 8 | 9 | class Platform(PlatformBase): 10 | def __init__(self): 11 | PlatformBase.__init__(self) 12 | self._chassis = Chassis() 13 | -------------------------------------------------------------------------------- /sonic-syseepromd/tests/mocked_libs/swsscommon/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Mock implementation of swsscommon package for unit testing 3 | ''' 4 | 5 | from . import swsscommon 6 | -------------------------------------------------------------------------------- /sonic-syseepromd/tests/mocked_libs/swsscommon/swsscommon.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Mock implementation of swsscommon package for unit testing 3 | ''' 4 | 5 | from swsssdk import ConfigDBConnector, SonicDBConfig, SonicV2Connector 6 | 7 | STATE_DB = '' 8 | 9 | 10 | class Table: 11 | def __init__(self, db, table_name): 12 | self.table_name = table_name 13 | self.mock_dict = {} 14 | 15 | def _del(self, key): 16 | del self.mock_dict[key] 17 | pass 18 | 19 | def set(self, key, fvs): 20 | self.mock_dict[key] = fvs.fv_dict 21 | pass 22 | 23 | def get(self, key): 24 | if key in self.mock_dict: 25 | return self.mock_dict[key] 26 | return None 27 | 28 | def get_size(self): 29 | return (len(self.mock_dict)) 30 | 31 | 32 | class FieldValuePairs: 33 | fv_dict = {} 34 | 35 | def __init__(self, tuple_list): 36 | if isinstance(tuple_list, list) and isinstance(tuple_list[0], tuple): 37 | self.fv_dict = dict(tuple_list) 38 | 39 | def __setitem__(self, key, kv_tuple): 40 | self.fv_dict[kv_tuple[0]] = kv_tuple[1] 41 | 42 | def __getitem__(self, key): 43 | return self.fv_dict[key] 44 | 45 | def __eq__(self, other): 46 | if not isinstance(other, FieldValuePairs): 47 | # don't attempt to compare against unrelated types 48 | return NotImplemented 49 | 50 | return self.fv_dict == other.fv_dict 51 | 52 | def __repr__(self): 53 | return repr(self.fv_dict) 54 | 55 | def __str__(self): 56 | return repr(self.fv_dict) 57 | -------------------------------------------------------------------------------- /sonic-syseepromd/tests/test_syseepromd.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from imp import load_source # Replace with importlib once we no longer need to support Python 2 4 | 5 | import pytest 6 | 7 | # TODO: Clean this up once we no longer need to support Python 2 8 | if sys.version_info.major == 3: 9 | from unittest import mock 10 | else: 11 | import mock 12 | 13 | SYSLOG_IDENTIFIER = 'syseepromd_test' 14 | NOT_AVAILABLE = 'N/A' 15 | 16 | tests_path = os.path.dirname(os.path.abspath(__file__)) 17 | 18 | # Add mocked_libs path so that the file under test can load mocked modules from there 19 | mocked_libs_path = os.path.join(tests_path, 'mocked_libs') 20 | sys.path.insert(0, mocked_libs_path) 21 | 22 | from sonic_py_common import daemon_base 23 | daemon_base.db_connect = mock.MagicMock() 24 | 25 | # Add path to the file under test so that we can load it 26 | modules_path = os.path.dirname(tests_path) 27 | scripts_path = os.path.join(modules_path, 'scripts') 28 | sys.path.insert(0, modules_path) 29 | 30 | load_source('syseepromd', os.path.join(scripts_path, 'syseepromd')) 31 | import syseepromd 32 | 33 | 34 | def test_post_eeprom_to_db_eeprom_read_fail(): 35 | daemon_syseepromd = syseepromd.DaemonSyseeprom() 36 | daemon_syseepromd.eeprom.read_eeprom = mock.MagicMock(return_value=None) 37 | daemon_syseepromd.eeprom_tbl = mock.MagicMock() 38 | daemon_syseepromd.log_error = mock.MagicMock() 39 | 40 | ret = daemon_syseepromd.post_eeprom_to_db() 41 | assert ret == syseepromd.ERR_FAILED_EEPROM 42 | assert daemon_syseepromd.log_error.call_count == 1 43 | daemon_syseepromd.log_error.assert_called_with('Failed to read EEPROM') 44 | assert daemon_syseepromd.eeprom_tbl.getKeys.call_count == 0 45 | 46 | 47 | def test_post_eeprom_to_db_update_fail(): 48 | daemon_syseepromd = syseepromd.DaemonSyseeprom() 49 | daemon_syseepromd.eeprom.update_eeprom_db = mock.MagicMock(return_value=1) 50 | daemon_syseepromd.eeprom_tbl = mock.MagicMock() 51 | daemon_syseepromd.log_error = mock.MagicMock() 52 | 53 | ret = daemon_syseepromd.post_eeprom_to_db() 54 | assert ret == syseepromd.ERR_FAILED_UPDATE_DB 55 | assert daemon_syseepromd.log_error.call_count == 1 56 | daemon_syseepromd.log_error.assert_called_with('Failed to update EEPROM info in database') 57 | assert daemon_syseepromd.eeprom_tbl.getKeys.call_count == 0 58 | 59 | 60 | def test_post_eeprom_to_db_ok(): 61 | daemon_syseepromd = syseepromd.DaemonSyseeprom() 62 | daemon_syseepromd.eeprom.update_eeprom_db = mock.MagicMock(return_value=0) 63 | daemon_syseepromd.eeprom_tbl = mock.MagicMock() 64 | daemon_syseepromd.log_error = mock.MagicMock() 65 | 66 | ret = daemon_syseepromd.post_eeprom_to_db() 67 | assert ret == syseepromd.ERR_NONE 68 | assert daemon_syseepromd.log_error.call_count == 0 69 | assert daemon_syseepromd.eeprom_tbl.getKeys.call_count == 1 70 | 71 | 72 | def test_clear_db(): 73 | daemon_syseepromd = syseepromd.DaemonSyseeprom() 74 | daemon_syseepromd.eeprom_tbl.getKeys = mock.MagicMock(return_value=['key1', 'key2']) 75 | daemon_syseepromd.eeprom_tbl._del = mock.MagicMock() 76 | 77 | daemon_syseepromd.clear_db() 78 | assert daemon_syseepromd.eeprom_tbl.getKeys.call_count == 1 79 | assert daemon_syseepromd.eeprom_tbl._del.call_count == 2 80 | 81 | 82 | def test_detect_eeprom_table_integrity(): 83 | daemon_syseepromd = syseepromd.DaemonSyseeprom() 84 | 85 | # Test entries as expected 86 | daemon_syseepromd.eeprom_tbl.getKeys = mock.MagicMock(return_value=['key1', 'key2']) 87 | daemon_syseepromd.eepromtbl_keys = ['key1', 'key2'] 88 | ret = daemon_syseepromd.detect_eeprom_table_integrity() 89 | assert ret == True 90 | 91 | # Test differing amounts of entries 92 | daemon_syseepromd.eeprom_tbl.getKeys = mock.MagicMock(return_value=['key1', 'key2']) 93 | daemon_syseepromd.eepromtbl_keys = ['key1'] 94 | ret = daemon_syseepromd.detect_eeprom_table_integrity() 95 | assert ret == False 96 | 97 | # Test same amount of entries, but with different keys 98 | daemon_syseepromd.eeprom_tbl.getKeys = mock.MagicMock(return_value=['key1', 'key2']) 99 | daemon_syseepromd.eepromtbl_keys = ['key1', 'key3'] 100 | ret = daemon_syseepromd.detect_eeprom_table_integrity() 101 | assert ret == False 102 | 103 | 104 | def test_signal_handler(): 105 | daemon_syseepromd = syseepromd.DaemonSyseeprom() 106 | daemon_syseepromd.stop_event.set = mock.MagicMock() 107 | daemon_syseepromd.log_info = mock.MagicMock() 108 | daemon_syseepromd.log_warning = mock.MagicMock() 109 | 110 | # Test SIGHUP 111 | daemon_syseepromd.signal_handler(syseepromd.signal.SIGHUP, None) 112 | assert daemon_syseepromd.log_info.call_count == 1 113 | daemon_syseepromd.log_info.assert_called_with("Caught signal 'SIGHUP' - ignoring...") 114 | assert daemon_syseepromd.log_warning.call_count == 0 115 | assert daemon_syseepromd.stop_event.set.call_count == 0 116 | assert syseepromd.exit_code == 0 117 | 118 | # Reset 119 | daemon_syseepromd.log_info.reset_mock() 120 | daemon_syseepromd.log_warning.reset_mock() 121 | daemon_syseepromd.stop_event.set.reset_mock() 122 | 123 | # Test SIGINT 124 | test_signal = syseepromd.signal.SIGINT 125 | daemon_syseepromd.signal_handler(test_signal, None) 126 | assert daemon_syseepromd.log_info.call_count == 1 127 | daemon_syseepromd.log_info.assert_called_with("Caught signal 'SIGINT' - exiting...") 128 | assert daemon_syseepromd.log_warning.call_count == 0 129 | assert daemon_syseepromd.stop_event.set.call_count == 1 130 | assert syseepromd.exit_code == (128 + test_signal) 131 | 132 | # Reset 133 | daemon_syseepromd.log_info.reset_mock() 134 | daemon_syseepromd.log_warning.reset_mock() 135 | daemon_syseepromd.stop_event.set.reset_mock() 136 | 137 | # Test SIGTERM 138 | test_signal = syseepromd.signal.SIGTERM 139 | daemon_syseepromd.signal_handler(test_signal, None) 140 | assert daemon_syseepromd.log_info.call_count == 1 141 | daemon_syseepromd.log_info.assert_called_with("Caught signal 'SIGTERM' - exiting...") 142 | assert daemon_syseepromd.log_warning.call_count == 0 143 | assert daemon_syseepromd.stop_event.set.call_count == 1 144 | assert syseepromd.exit_code == (128 + test_signal) 145 | 146 | # Reset 147 | daemon_syseepromd.log_info.reset_mock() 148 | daemon_syseepromd.log_warning.reset_mock() 149 | daemon_syseepromd.stop_event.set.reset_mock() 150 | syseepromd.exit_code = 0 151 | 152 | # Test an unhandled signal 153 | daemon_syseepromd.signal_handler(syseepromd.signal.SIGUSR1, None) 154 | assert daemon_syseepromd.log_warning.call_count == 1 155 | daemon_syseepromd.log_warning.assert_called_with("Caught unhandled signal 'SIGUSR1' - ignoring...") 156 | assert daemon_syseepromd.log_info.call_count == 0 157 | assert daemon_syseepromd.stop_event.set.call_count == 0 158 | assert syseepromd.exit_code == 0 159 | 160 | 161 | @mock.patch('syseepromd.EEPROM_INFO_UPDATE_PERIOD_SECS', 1) 162 | def test_run(): 163 | daemon_syseepromd = syseepromd.DaemonSyseeprom() 164 | daemon_syseepromd.clear_db = mock.MagicMock() 165 | daemon_syseepromd.log_info = mock.MagicMock() 166 | daemon_syseepromd.log_error = mock.MagicMock() 167 | daemon_syseepromd.post_eeprom_to_db = mock.MagicMock(return_value=syseepromd.ERR_NONE) 168 | 169 | # Test no change to EEPROM data 170 | daemon_syseepromd.detect_eeprom_table_integrity = mock.MagicMock(return_value=True) 171 | 172 | ret = daemon_syseepromd.run() 173 | assert ret == True 174 | assert daemon_syseepromd.detect_eeprom_table_integrity.call_count == 1 175 | assert daemon_syseepromd.log_info.call_count == 0 176 | assert daemon_syseepromd.log_error.call_count == 0 177 | assert daemon_syseepromd.clear_db.call_count == 0 178 | assert daemon_syseepromd.post_eeprom_to_db.call_count == 0 179 | 180 | # Reset mocks 181 | daemon_syseepromd.detect_eeprom_table_integrity.reset_mock() 182 | 183 | # Test EEPROM data has changed, update succeeds 184 | daemon_syseepromd.detect_eeprom_table_integrity = mock.MagicMock(return_value=False) 185 | 186 | ret = daemon_syseepromd.run() 187 | assert ret == True 188 | assert daemon_syseepromd.detect_eeprom_table_integrity.call_count == 1 189 | assert daemon_syseepromd.log_info.call_count == 1 190 | daemon_syseepromd.log_info.assert_called_with('System EEPROM table was changed, needs update') 191 | assert daemon_syseepromd.clear_db.call_count == 1 192 | assert daemon_syseepromd.post_eeprom_to_db.call_count == 1 193 | assert daemon_syseepromd.log_error.call_count == 0 194 | 195 | # Reset mocks 196 | daemon_syseepromd.detect_eeprom_table_integrity.reset_mock() 197 | daemon_syseepromd.log_info.reset_mock() 198 | daemon_syseepromd.clear_db.reset_mock() 199 | daemon_syseepromd.post_eeprom_to_db.reset_mock() 200 | 201 | # Test EEPROM data has changed, update fails 202 | daemon_syseepromd.detect_eeprom_table_integrity = mock.MagicMock(return_value=False) 203 | daemon_syseepromd.post_eeprom_to_db = mock.MagicMock(return_value=syseepromd.ERR_FAILED_UPDATE_DB) 204 | 205 | ret = daemon_syseepromd.run() 206 | assert ret == True 207 | assert daemon_syseepromd.detect_eeprom_table_integrity.call_count == 1 208 | assert daemon_syseepromd.log_info.call_count == 1 209 | daemon_syseepromd.log_info.assert_called_with('System EEPROM table was changed, needs update') 210 | assert daemon_syseepromd.clear_db.call_count == 1 211 | assert daemon_syseepromd.post_eeprom_to_db.call_count == 1 212 | assert daemon_syseepromd.log_error.call_count == 1 213 | daemon_syseepromd.log_error.assert_called_with('Failed to post EEPROM to database') 214 | 215 | 216 | @mock.patch('syseepromd.DaemonSyseeprom.run') 217 | def test_main(mock_run): 218 | mock_run.return_value = False 219 | 220 | syseepromd.main() 221 | assert mock_run.call_count == 1 222 | -------------------------------------------------------------------------------- /sonic-thermalctld/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml --junitxml=test-results.xml -vv 3 | -------------------------------------------------------------------------------- /sonic-thermalctld/setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | -------------------------------------------------------------------------------- /sonic-thermalctld/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='sonic-thermalctld', 5 | version='1.0', 6 | description='Thermal control daemon for SONiC', 7 | license='Apache 2.0', 8 | author='SONiC Team', 9 | author_email='linuxnetdev@microsoft.com', 10 | url='https://github.com/Azure/sonic-platform-daemons', 11 | maintainer='Junchao Chen', 12 | maintainer_email='junchao@mellanox.com', 13 | packages=[ 14 | 'tests' 15 | ], 16 | scripts=[ 17 | 'scripts/thermalctld', 18 | ], 19 | setup_requires=[ 20 | 'pytest-runner', 21 | 'wheel' 22 | ], 23 | tests_require=[ 24 | 'mock>=2.0.0; python_version < "3.3"', 25 | 'pytest', 26 | 'pytest-cov', 27 | 'sonic-platform-common' 28 | ], 29 | classifiers=[ 30 | 'Development Status :: 4 - Beta', 31 | 'Environment :: No Input/Output (Daemon)', 32 | 'Intended Audience :: Developers', 33 | 'Intended Audience :: Information Technology', 34 | 'Intended Audience :: System Administrators', 35 | 'License :: OSI Approved :: Apache Software License', 36 | 'Natural Language :: English', 37 | 'Operating System :: POSIX :: Linux', 38 | 'Programming Language :: Python :: 2.7', 39 | 'Programming Language :: Python :: 3.7', 40 | 'Topic :: System :: Hardware', 41 | ], 42 | keywords='sonic SONiC THERMALCONTROL thermalcontrol THERMALCTL thermalctl thermalctld', 43 | test_suite='setup.get_test_suite' 44 | ) 45 | -------------------------------------------------------------------------------- /sonic-thermalctld/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-thermalctld/tests/__init__.py -------------------------------------------------------------------------------- /sonic-thermalctld/tests/mock_swsscommon.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Mock implementation of swsscommon package for unit testing 3 | ''' 4 | 5 | STATE_DB = '' 6 | CHASSIS_STATE_DB = '' 7 | 8 | 9 | class Table: 10 | def __init__(self, db, table_name): 11 | self.table_name = table_name 12 | self.mock_dict = {} 13 | 14 | def _del(self, key): 15 | del self.mock_dict[key] 16 | pass 17 | 18 | def set(self, key, fvs): 19 | self.mock_dict[key] = fvs.fv_dict 20 | pass 21 | 22 | def get(self, key): 23 | if key in self.mock_dict: 24 | return self.mock_dict[key] 25 | return None 26 | 27 | def get_size(self): 28 | return (len(self.mock_dict)) 29 | 30 | 31 | class FieldValuePairs: 32 | def __init__(self, fvs): 33 | self.fv_dict = dict(fvs) 34 | pass 35 | -------------------------------------------------------------------------------- /sonic-thermalctld/tests/mocked_libs/sonic_platform/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mock implementation of sonic_platform package for unit testing 3 | """ 4 | 5 | from . import chassis 6 | from . import platform 7 | -------------------------------------------------------------------------------- /sonic-thermalctld/tests/mocked_libs/sonic_platform/chassis.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mock implementation of sonic_platform package for unit testing 3 | """ 4 | 5 | # TODO: Clean this up once we no longer need to support Python 2 6 | import sys 7 | if sys.version_info.major == 3: 8 | from unittest import mock 9 | else: 10 | import mock 11 | 12 | from sonic_platform_base.chassis_base import ChassisBase 13 | 14 | 15 | class Chassis(ChassisBase): 16 | def __init__(self): 17 | ChassisBase.__init__(self) 18 | self._eeprom = mock.MagicMock() 19 | self._thermal_manager = mock.MagicMock() 20 | 21 | def get_eeprom(self): 22 | return self._eeprom 23 | 24 | def get_thermal_manager(self): 25 | return self._thermal_manager 26 | -------------------------------------------------------------------------------- /sonic-thermalctld/tests/mocked_libs/sonic_platform/platform.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mock implementation of sonic_platform package for unit testing 3 | """ 4 | 5 | from sonic_platform_base.platform_base import PlatformBase 6 | from sonic_platform.chassis import Chassis 7 | 8 | class Platform(PlatformBase): 9 | def __init__(self): 10 | PlatformBase.__init__(self) 11 | self._chassis = Chassis() 12 | -------------------------------------------------------------------------------- /sonic-thermalctld/tests/mocked_libs/swsscommon/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Mock implementation of swsscommon package for unit testing 3 | ''' 4 | 5 | from . import swsscommon 6 | -------------------------------------------------------------------------------- /sonic-thermalctld/tests/mocked_libs/swsscommon/swsscommon.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Mock implementation of swsscommon package for unit testing 3 | ''' 4 | 5 | from swsssdk import ConfigDBConnector, SonicDBConfig, SonicV2Connector 6 | 7 | STATE_DB = '' 8 | 9 | 10 | class Table: 11 | def __init__(self, db, table_name): 12 | self.table_name = table_name 13 | self.mock_dict = {} 14 | 15 | def _del(self, key): 16 | del self.mock_dict[key] 17 | pass 18 | 19 | def set(self, key, fvs): 20 | self.mock_dict[key] = fvs.fv_dict 21 | pass 22 | 23 | def get(self, key): 24 | if key in self.mock_dict: 25 | return self.mock_dict[key] 26 | return None 27 | 28 | def get_size(self): 29 | return (len(self.mock_dict)) 30 | 31 | 32 | class FieldValuePairs: 33 | fv_dict = {} 34 | 35 | def __init__(self, tuple_list): 36 | if isinstance(tuple_list, list) and isinstance(tuple_list[0], tuple): 37 | self.fv_dict = dict(tuple_list) 38 | 39 | def __setitem__(self, key, kv_tuple): 40 | self.fv_dict[kv_tuple[0]] = kv_tuple[1] 41 | 42 | def __getitem__(self, key): 43 | return self.fv_dict[key] 44 | 45 | def __eq__(self, other): 46 | if not isinstance(other, FieldValuePairs): 47 | # don't attempt to compare against unrelated types 48 | return NotImplemented 49 | 50 | return self.fv_dict == other.fv_dict 51 | 52 | def __repr__(self): 53 | return repr(self.fv_dict) 54 | 55 | def __str__(self): 56 | return repr(self.fv_dict) 57 | -------------------------------------------------------------------------------- /sonic-xcvrd/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --cov=xcvrd --cov-report html --cov-report term --cov-report xml --junitxml=test-results.xml -v 3 | -------------------------------------------------------------------------------- /sonic-xcvrd/setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | -------------------------------------------------------------------------------- /sonic-xcvrd/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='sonic-xcvrd', 5 | version='1.0', 6 | description='Transceiver monitoring daemon for SONiC', 7 | license='Apache 2.0', 8 | author='SONiC Team', 9 | author_email='linuxnetdev@microsoft.com', 10 | url='https://github.com/Azure/sonic-platform-daemons', 11 | maintainer='Kebo Liu', 12 | maintainer_email='kebol@mellanox.com', 13 | packages=find_packages(), 14 | entry_points={ 15 | 'console_scripts': [ 16 | 'xcvrd = xcvrd.xcvrd:main', 17 | ] 18 | }, 19 | install_requires=[ 20 | # NOTE: This package also requires swsscommon, but it is not currently installed as a wheel 21 | 'enum34; python_version < "3.4"', 22 | 'sonic-py-common', 23 | ], 24 | setup_requires=[ 25 | 'wheel' 26 | ], 27 | tests_require=[ 28 | 'pytest', 29 | 'pytest-cov', 30 | ], 31 | classifiers=[ 32 | 'Development Status :: 4 - Beta', 33 | 'Environment :: No Input/Output (Daemon)', 34 | 'Intended Audience :: Developers', 35 | 'Intended Audience :: Information Technology', 36 | 'Intended Audience :: System Administrators', 37 | 'License :: OSI Approved :: Apache Software License', 38 | 'Natural Language :: English', 39 | 'Operating System :: POSIX :: Linux', 40 | 'Programming Language :: Python :: 3.7', 41 | 'Topic :: System :: Hardware', 42 | ], 43 | keywords='sonic SONiC TRANSCEIVER transceiver daemon XCVRD xcvrd', 44 | ) 45 | -------------------------------------------------------------------------------- /sonic-xcvrd/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-xcvrd/tests/__init__.py -------------------------------------------------------------------------------- /sonic-xcvrd/tests/media_settings_extended_format.json: -------------------------------------------------------------------------------- 1 | { 2 | "GLOBAL_MEDIA_SETTINGS": { 3 | "0-31": { 4 | "QSFP-DD-sm_media_interface": { 5 | "speed:400GAUI-8": { 6 | "idriver": { 7 | "lane0": "0x0000003c", 8 | "lane1": "0x0000003c", 9 | "lane2": "0x0000003c", 10 | "lane3": "0x0000003c", 11 | "lane4": "0x0000003c", 12 | "lane5": "0x0000003c", 13 | "lane6": "0x0000003c", 14 | "lane7": "0x0000003c" 15 | }, 16 | "pre1": { 17 | "lane0": "0x00000002", 18 | "lane1": "0x00000002", 19 | "lane2": "0x00000002", 20 | "lane3": "0x00000002", 21 | "lane4": "0x00000002", 22 | "lane5": "0x00000002", 23 | "lane6": "0x00000002", 24 | "lane7": "0x00000002" 25 | }, 26 | "ob_m2lp": { 27 | "lane0": "0x0000000e", 28 | "lane1": "0x0000000e", 29 | "lane2": "0x0000000e", 30 | "lane3": "0x0000000e", 31 | "lane4": "0x0000000e", 32 | "lane5": "0x0000000e", 33 | "lane6": "0x0000000e", 34 | "lane7": "0x0000000e" 35 | } 36 | } 37 | }, 38 | "QSFP-DD-active_cable_media_interface":{ 39 | "speed:100GAUI-2": { 40 | "pre1": { 41 | "lane0": "0x00000002", 42 | "lane1": "0x00000002" 43 | }, 44 | "main": { 45 | "lane0": "0x00000020", 46 | "lane1": "0x00000020" 47 | }, 48 | "post1": { 49 | "lane0": "0x00000006", 50 | "lane1": "0x00000006" 51 | }, 52 | "regn_bfm1n": { 53 | "lane0": "0x000000aa", 54 | "lane1": "0x000000aa" 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /sonic-xcvrd/tests/mock_platform.py: -------------------------------------------------------------------------------- 1 | class MockDevice: 2 | def __init__(self): 3 | self.name = None 4 | self.presence = True 5 | self.model = 'FAN Model' 6 | self.serial = 'Fan Serial' 7 | 8 | def get_name(self): 9 | return self.name 10 | 11 | def get_presence(self): 12 | return self.presence 13 | 14 | def get_model(self): 15 | return self.model 16 | 17 | def get_serial(self): 18 | return self.serial 19 | 20 | def get_position_in_parent(self): 21 | return 1 22 | 23 | def is_replaceable(self): 24 | return True 25 | 26 | def get_status(self): 27 | return True 28 | 29 | 30 | class MockFan(MockDevice): 31 | STATUS_LED_COLOR_RED = 'red' 32 | STATUS_LED_COLOR_GREEN = 'green' 33 | 34 | def __init__(self): 35 | MockDevice.__init__(self) 36 | self.speed = 20 37 | self.speed_tolerance = 20 38 | self.target_speed = 20 39 | self.status = True 40 | self.direction = 'intake' 41 | self.led_status = 'red' 42 | 43 | def get_speed(self): 44 | return self.speed 45 | 46 | def get_speed_tolerance(self): 47 | return self.speed_tolerance 48 | 49 | def get_target_speed(self): 50 | return self.target_speed 51 | 52 | def get_status(self): 53 | return self.status 54 | 55 | def get_direction(self): 56 | return self.direction 57 | 58 | def get_status_led(self): 59 | return self.led_status 60 | 61 | def set_status_led(self, value): 62 | self.led_status = value 63 | 64 | def make_under_speed(self): 65 | self.speed = 1 66 | self.target_speed = 2 67 | self.speed_tolerance = 0 68 | 69 | def make_over_speed(self): 70 | self.speed = 2 71 | self.target_speed = 1 72 | self.speed_tolerance = 0 73 | 74 | def make_normal_speed(self): 75 | self.speed = 1 76 | self.target_speed = 1 77 | self.speed_tolerance = 0 78 | 79 | 80 | class MockErrorFan(MockFan): 81 | def get_speed(self): 82 | raise Exception('Fail to get speed') 83 | 84 | 85 | class MockPsu(MockDevice): 86 | def __init__(self): 87 | MockDevice.__init__(self) 88 | self.fan_list = [] 89 | 90 | def get_all_fans(self): 91 | return self.fan_list 92 | 93 | 94 | class MockFanDrawer(MockDevice): 95 | def __init__(self, index): 96 | MockDevice.__init__(self) 97 | self.name = 'FanDrawer {}'.format(index) 98 | self.fan_list = [] 99 | self.led_status = 'red' 100 | 101 | def get_name(self): 102 | return self.name 103 | 104 | def get_all_fans(self): 105 | return self.fan_list 106 | 107 | def get_status_led(self): 108 | return self.led_status 109 | 110 | def set_status_led(self, value): 111 | self.led_status = value 112 | 113 | 114 | class MockThermal(MockDevice): 115 | def __init__(self, index=None): 116 | MockDevice.__init__(self) 117 | self.name = None 118 | self.name = 'Thermal {}'.format(index) if index != None else None 119 | self.temperature = 2 120 | self.minimum_temperature = 1 121 | self.maximum_temperature = 5 122 | self.high_threshold = 3 123 | self.low_threshold = 1 124 | self.high_critical_threshold = 4 125 | self.low_critical_threshold = 0 126 | 127 | def get_name(self): 128 | return self.name 129 | 130 | def get_temperature(self): 131 | return self.temperature 132 | 133 | def get_minimum_recorded(self): 134 | return self.minimum_temperature 135 | 136 | def get_maximum_recorded(self): 137 | return self.maximum_temperature 138 | 139 | def get_high_threshold(self): 140 | return self.high_threshold 141 | 142 | def get_low_threshold(self): 143 | return self.low_threshold 144 | 145 | def get_high_critical_threshold(self): 146 | return self.high_critical_threshold 147 | 148 | def get_low_critical_threshold(self): 149 | return self.low_critical_threshold 150 | 151 | def make_over_temper(self): 152 | self.high_threshold = 2 153 | self.temperature = 3 154 | self.low_threshold = 1 155 | 156 | def make_under_temper(self): 157 | self.high_threshold = 3 158 | self.temperature = 1 159 | self.low_threshold = 2 160 | 161 | def make_normal_temper(self): 162 | self.high_threshold = 3 163 | self.temperature = 2 164 | self.low_threshold = 1 165 | 166 | 167 | class MockErrorThermal(MockThermal): 168 | def get_temperature(self): 169 | raise Exception('Fail to get temperature') 170 | 171 | 172 | class MockChassis: 173 | def __init__(self): 174 | self.fan_list = [] 175 | self.psu_list = [] 176 | self.thermal_list = [] 177 | self.fan_drawer_list = [] 178 | self.sfp_list = [] 179 | self.is_chassis_system = False 180 | 181 | def get_all_fans(self): 182 | return self.fan_list 183 | 184 | def get_all_psus(self): 185 | return self.psu_list 186 | 187 | def get_all_thermals(self): 188 | return self.thermal_list 189 | 190 | def get_all_fan_drawers(self): 191 | return self.fan_drawer_list 192 | 193 | def get_all_sfps(self): 194 | return self.sfp_list 195 | 196 | def get_num_thermals(self): 197 | return len(self.thermal_list) 198 | 199 | def make_absence_fan(self): 200 | fan = MockFan() 201 | fan.presence = False 202 | fan_drawer = MockFanDrawer(len(self.fan_drawer_list)) 203 | fan_drawer.fan_list.append(fan) 204 | self.fan_list.append(fan) 205 | self.fan_drawer_list.append(fan_drawer) 206 | 207 | def make_fault_fan(self): 208 | fan = MockFan() 209 | fan.status = False 210 | fan_drawer = MockFanDrawer(len(self.fan_drawer_list)) 211 | fan_drawer.fan_list.append(fan) 212 | self.fan_list.append(fan) 213 | self.fan_drawer_list.append(fan_drawer) 214 | 215 | def make_under_speed_fan(self): 216 | fan = MockFan() 217 | fan.make_under_speed() 218 | fan_drawer = MockFanDrawer(len(self.fan_drawer_list)) 219 | fan_drawer.fan_list.append(fan) 220 | self.fan_list.append(fan) 221 | self.fan_drawer_list.append(fan_drawer) 222 | 223 | def make_over_speed_fan(self): 224 | fan = MockFan() 225 | fan.make_over_speed() 226 | fan_drawer = MockFanDrawer(len(self.fan_drawer_list)) 227 | fan_drawer.fan_list.append(fan) 228 | self.fan_list.append(fan) 229 | self.fan_drawer_list.append(fan_drawer) 230 | 231 | def make_error_fan(self): 232 | fan = MockErrorFan() 233 | fan_drawer = MockFanDrawer(len(self.fan_drawer_list)) 234 | fan_drawer.fan_list.append(fan) 235 | self.fan_list.append(fan) 236 | self.fan_drawer_list.append(fan_drawer) 237 | 238 | def make_over_temper_thermal(self): 239 | thermal = MockThermal() 240 | thermal.make_over_temper() 241 | self.thermal_list.append(thermal) 242 | 243 | def make_under_temper_thermal(self): 244 | thermal = MockThermal() 245 | thermal.make_under_temper() 246 | self.thermal_list.append(thermal) 247 | 248 | def make_error_thermal(self): 249 | thermal = MockErrorThermal() 250 | self.thermal_list.append(thermal) 251 | 252 | def is_modular_chassis(self): 253 | return self.is_chassis_system 254 | 255 | def set_modular_chassis(self, is_true): 256 | self.is_chassis_system = is_true 257 | 258 | def set_my_slot(self, my_slot): 259 | self.my_slot = my_slot 260 | 261 | def get_my_slot(self): 262 | return self.my_slot 263 | -------------------------------------------------------------------------------- /sonic-xcvrd/tests/mock_swsscommon.py: -------------------------------------------------------------------------------- 1 | STATE_DB = '' 2 | CHASSIS_STATE_DB = '' 3 | 4 | 5 | class Table: 6 | def __init__(self, db, table_name): 7 | self.table_name = table_name 8 | self.mock_dict = {} 9 | self.mock_keys = [] 10 | 11 | def _del(self, key): 12 | if key in self.mock_dict: 13 | del self.mock_dict[key] 14 | self.mock_keys.remove(key) 15 | pass 16 | 17 | def hdel(self, key, field): 18 | if key not in self.mock_dict: 19 | return 20 | 21 | # swsscommon.FieldValuePairs 22 | fvs = self.mock_dict[key] 23 | for i, fv in enumerate(fvs): 24 | if fv[0] == field: 25 | del fvs[i] 26 | break 27 | if self.get_size_for_key(key) == 0: 28 | self._del(key) 29 | 30 | def set(self, key, fvs): 31 | self.mock_dict[key] = fvs 32 | self.mock_keys.append(key) 33 | pass 34 | 35 | def get(self, key): 36 | if key in self.mock_dict: 37 | return True, self.mock_dict[key] 38 | return False, None 39 | 40 | def hget(self, key, field): 41 | if key in self.mock_dict: 42 | for fv in self.mock_dict[key]: 43 | if fv[0] == field: 44 | return True, fv[1] 45 | return False, None 46 | 47 | def get_size(self): 48 | return (len(self.mock_dict)) 49 | 50 | def get_size_for_key(self, key): 51 | return len(self.mock_dict[key]) 52 | 53 | def getKeys(self): 54 | return self.mock_keys 55 | -------------------------------------------------------------------------------- /sonic-xcvrd/tests/optics_si_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "GLOBAL_MEDIA_SETTINGS":{ 3 | "0-31":{ 4 | "100G_SPEED":{ 5 | "CREDO-CAC82X321M2MC0HW":{ 6 | "OutputEqPreCursorTargetRx":{ 7 | "OutputEqPreCursorTargetRx1":3, 8 | "OutputEqPreCursorTargetRx2":3, 9 | "OutputEqPreCursorTargetRx3":3, 10 | "OutputEqPreCursorTargetRx4":3, 11 | "OutputEqPreCursorTargetRx5":3, 12 | "OutputEqPreCursorTargetRx6":3, 13 | "OutputEqPreCursorTargetRx7":3, 14 | "OutputEqPreCursorTargetRx8":3 15 | }, 16 | "OutputEqPostCursorTargetRx":{ 17 | "OutputEqPostCursorTargetRx1":0, 18 | "OutputEqPostCursorTargetRx2":0, 19 | "OutputEqPostCursorTargetRx3":0, 20 | "OutputEqPostCursorTargetRx4":0, 21 | "OutputEqPostCursorTargetRx5":0, 22 | "OutputEqPostCursorTargetRx6":0, 23 | "OutputEqPostCursorTargetRx7":0, 24 | "OutputEqPostCursorTargetRx8":0 25 | }, 26 | "OutputAmplitudeTargetRx":{ 27 | "OutputAmplitudeTargetRx1":0, 28 | "OutputAmplitudeTargetRx2":0, 29 | "OutputAmplitudeTargetRx3":0, 30 | "OutputAmplitudeTargetRx4":0, 31 | "OutputAmplitudeTargetRx5":0, 32 | "OutputAmplitudeTargetRx6":0, 33 | "OutputAmplitudeTargetRx7":0, 34 | "OutputAmplitudeTargetRx8":0 35 | } 36 | } 37 | } 38 | } 39 | }, 40 | "PORT_MEDIA_SETTINGS":{ 41 | "0":{ 42 | "100G_SPEED":{ 43 | "CREDO-CAC82X321M2MC0HW":{ 44 | "OutputEqPreCursorTargetRx":{ 45 | "OutputEqPreCursorTargetRx1":3, 46 | "OutputEqPreCursorTargetRx2":3, 47 | "OutputEqPreCursorTargetRx3":3, 48 | "OutputEqPreCursorTargetRx4":3, 49 | "OutputEqPreCursorTargetRx5":3, 50 | "OutputEqPreCursorTargetRx6":3, 51 | "OutputEqPreCursorTargetRx7":3, 52 | "OutputEqPreCursorTargetRx8":3 53 | }, 54 | "OutputEqPostCursorTargetRx":{ 55 | "OutputEqPostCursorTargetRx1":0, 56 | "OutputEqPostCursorTargetRx2":0, 57 | "OutputEqPostCursorTargetRx3":0, 58 | "OutputEqPostCursorTargetRx4":0, 59 | "OutputEqPostCursorTargetRx5":0, 60 | "OutputEqPostCursorTargetRx6":0, 61 | "OutputEqPostCursorTargetRx7":0, 62 | "OutputEqPostCursorTargetRx8":0 63 | }, 64 | "OutputAmplitudeTargetRx":{ 65 | "OutputAmplitudeTargetRx1":0, 66 | "OutputAmplitudeTargetRx2":0, 67 | "OutputAmplitudeTargetRx3":0, 68 | "OutputAmplitudeTargetRx4":0, 69 | "OutputAmplitudeTargetRx5":0, 70 | "OutputAmplitudeTargetRx6":0, 71 | "OutputAmplitudeTargetRx7":0, 72 | "OutputAmplitudeTargetRx8":0 73 | } 74 | } 75 | } 76 | }, 77 | "1":{ 78 | "100G_SPEED":{ 79 | "Default":{ 80 | "OutputEqPreCursorTargetRx":{ 81 | "OutputEqPreCursorTargetRx1":3, 82 | "OutputEqPreCursorTargetRx2":3, 83 | "OutputEqPreCursorTargetRx3":3, 84 | "OutputEqPreCursorTargetRx4":3, 85 | "OutputEqPreCursorTargetRx5":3, 86 | "OutputEqPreCursorTargetRx6":3, 87 | "OutputEqPreCursorTargetRx7":3, 88 | "OutputEqPreCursorTargetRx8":3 89 | }, 90 | "OutputEqPostCursorTargetRx":{ 91 | "OutputEqPostCursorTargetRx1":0, 92 | "OutputEqPostCursorTargetRx2":0, 93 | "OutputEqPostCursorTargetRx3":0, 94 | "OutputEqPostCursorTargetRx4":0, 95 | "OutputEqPostCursorTargetRx5":0, 96 | "OutputEqPostCursorTargetRx6":0, 97 | "OutputEqPostCursorTargetRx7":0, 98 | "OutputEqPostCursorTargetRx8":0 99 | }, 100 | "OutputAmplitudeTargetRx":{ 101 | "OutputAmplitudeTargetRx1":1, 102 | "OutputAmplitudeTargetRx2":1, 103 | "OutputAmplitudeTargetRx3":1, 104 | "OutputAmplitudeTargetRx4":1, 105 | "OutputAmplitudeTargetRx5":1, 106 | "OutputAmplitudeTargetRx6":1, 107 | "OutputAmplitudeTargetRx7":1, 108 | "OutputAmplitudeTargetRx8":1 109 | } 110 | } 111 | } 112 | }, 113 | "10":{ 114 | "100G_SPEED":{ 115 | "Default":{ 116 | "OutputEqPreCursorTargetRx":{ 117 | "OutputEqPreCursorTargetRx1":3, 118 | "OutputEqPreCursorTargetRx2":3, 119 | "OutputEqPreCursorTargetRx3":3, 120 | "OutputEqPreCursorTargetRx4":3, 121 | "OutputEqPreCursorTargetRx5":3, 122 | "OutputEqPreCursorTargetRx6":3, 123 | "OutputEqPreCursorTargetRx7":3, 124 | "OutputEqPreCursorTargetRx8":3 125 | }, 126 | "OutputEqPostCursorTargetRx":{ 127 | "OutputEqPostCursorTargetRx1":0, 128 | "OutputEqPostCursorTargetRx2":0, 129 | "OutputEqPostCursorTargetRx3":0, 130 | "OutputEqPostCursorTargetRx4":0, 131 | "OutputEqPostCursorTargetRx5":0, 132 | "OutputEqPostCursorTargetRx6":0, 133 | "OutputEqPostCursorTargetRx7":0, 134 | "OutputEqPostCursorTargetRx8":0 135 | }, 136 | "OutputAmplitudeTargetRx":{ 137 | "OutputAmplitudeTargetRx1":1, 138 | "OutputAmplitudeTargetRx2":1, 139 | "OutputAmplitudeTargetRx3":1, 140 | "OutputAmplitudeTargetRx4":1, 141 | "OutputAmplitudeTargetRx5":1, 142 | "OutputAmplitudeTargetRx6":1, 143 | "OutputAmplitudeTargetRx7":1, 144 | "OutputAmplitudeTargetRx8":1 145 | } 146 | } 147 | } 148 | }, 149 | "11":{ 150 | "100G_SPEED":{ 151 | "Default":{ 152 | "OutputEqPreCursorTargetRx":{ 153 | "OutputEqPreCursorTargetRx1":3, 154 | "OutputEqPreCursorTargetRx2":3, 155 | "OutputEqPreCursorTargetRx3":3, 156 | "OutputEqPreCursorTargetRx4":3, 157 | "OutputEqPreCursorTargetRx5":3, 158 | "OutputEqPreCursorTargetRx6":3, 159 | "OutputEqPreCursorTargetRx7":3, 160 | "OutputEqPreCursorTargetRx8":3 161 | }, 162 | "OutputEqPostCursorTargetRx":{ 163 | "OutputEqPostCursorTargetRx1":0, 164 | "OutputEqPostCursorTargetRx2":0, 165 | "OutputEqPostCursorTargetRx3":0, 166 | "OutputEqPostCursorTargetRx4":0, 167 | "OutputEqPostCursorTargetRx5":0, 168 | "OutputEqPostCursorTargetRx6":0, 169 | "OutputEqPostCursorTargetRx7":0, 170 | "OutputEqPostCursorTargetRx8":0 171 | }, 172 | "OutputAmplitudeTargetRx":{ 173 | "OutputAmplitudeTargetRx1":1, 174 | "OutputAmplitudeTargetRx2":1, 175 | "OutputAmplitudeTargetRx3":1, 176 | "OutputAmplitudeTargetRx4":1, 177 | "OutputAmplitudeTargetRx5":1, 178 | "OutputAmplitudeTargetRx6":1, 179 | "OutputAmplitudeTargetRx7":1, 180 | "OutputAmplitudeTargetRx8":1 181 | } 182 | } 183 | } 184 | }, 185 | "12":{ 186 | "100G_SPEED":{ 187 | "Default":{ 188 | "OutputEqPreCursorTargetRx":{ 189 | "OutputEqPreCursorTargetRx1":3, 190 | "OutputEqPreCursorTargetRx2":3, 191 | "OutputEqPreCursorTargetRx3":3, 192 | "OutputEqPreCursorTargetRx4":3, 193 | "OutputEqPreCursorTargetRx5":3, 194 | "OutputEqPreCursorTargetRx6":3, 195 | "OutputEqPreCursorTargetRx7":3, 196 | "OutputEqPreCursorTargetRx8":3 197 | }, 198 | "OutputEqPostCursorTargetRx":{ 199 | "OutputEqPostCursorTargetRx1":0, 200 | "OutputEqPostCursorTargetRx2":0, 201 | "OutputEqPostCursorTargetRx3":0, 202 | "OutputEqPostCursorTargetRx4":0, 203 | "OutputEqPostCursorTargetRx5":0, 204 | "OutputEqPostCursorTargetRx6":0, 205 | "OutputEqPostCursorTargetRx7":0, 206 | "OutputEqPostCursorTargetRx8":0 207 | }, 208 | "OutputAmplitudeTargetRx":{ 209 | "OutputAmplitudeTargetRx1":1, 210 | "OutputAmplitudeTargetRx2":1, 211 | "OutputAmplitudeTargetRx3":1, 212 | "OutputAmplitudeTargetRx4":1, 213 | "OutputAmplitudeTargetRx5":1, 214 | "OutputAmplitudeTargetRx6":1, 215 | "OutputAmplitudeTargetRx7":1, 216 | "OutputAmplitudeTargetRx8":1 217 | } 218 | } 219 | } 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /sonic-xcvrd/tests/t0-sample-port-config.ini: -------------------------------------------------------------------------------- 1 | # name lanes alias 2 | Ethernet0 29,30,31,32 fortyGigE0/0 3 | Ethernet4 25,26,27,28 fortyGigE0/4 4 | Ethernet8 37,38,39,40 fortyGigE0/8 5 | Ethernet12 33,34,35,36 fortyGigE0/12 6 | Ethernet16 41,42,43,44 fortyGigE0/16 7 | Ethernet20 45,46,47,48 fortyGigE0/20 8 | Ethernet24 5,6,7,8 fortyGigE0/24 9 | Ethernet28 1,2,3,4 fortyGigE0/28 10 | Ethernet32 9,10,11,12 fortyGigE0/32 11 | Ethernet36 13,14,15,16 fortyGigE0/36 12 | Ethernet40 21,22,23,24 fortyGigE0/40 13 | Ethernet44 17,18,19,20 fortyGigE0/44 14 | Ethernet48 49,50,51,52 fortyGigE0/48 15 | -------------------------------------------------------------------------------- /sonic-xcvrd/xcvrd/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-xcvrd/xcvrd/__init__.py -------------------------------------------------------------------------------- /sonic-xcvrd/xcvrd/cmis/__init__.py: -------------------------------------------------------------------------------- 1 | from .cmis_manager_task import CmisManagerTask 2 | 3 | __all__ = ['CmisManagerTask'] 4 | -------------------------------------------------------------------------------- /sonic-xcvrd/xcvrd/dom/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-xcvrd/xcvrd/dom/__init__.py -------------------------------------------------------------------------------- /sonic-xcvrd/xcvrd/dom/utilities/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-xcvrd/xcvrd/dom/utilities/__init__.py -------------------------------------------------------------------------------- /sonic-xcvrd/xcvrd/dom/utilities/db/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-xcvrd/xcvrd/dom/utilities/db/__init__.py -------------------------------------------------------------------------------- /sonic-xcvrd/xcvrd/dom/utilities/dom_sensor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-xcvrd/xcvrd/dom/utilities/dom_sensor/__init__.py -------------------------------------------------------------------------------- /sonic-xcvrd/xcvrd/dom/utilities/dom_sensor/db_utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | from xcvrd.dom.utilities.db.utils import DBUtils 3 | from xcvrd.dom.utilities.dom_sensor.utils import DOMUtils 4 | from swsscommon import swsscommon 5 | 6 | 7 | class DOMDBUtils(DBUtils): 8 | """ 9 | This class provides utility functions for managing DB operations 10 | related to DOM on transceivers. 11 | Handles data related to the following tables: 12 | - TRANSCEIVER_DOM_SENSOR 13 | - TRANSCEIVER_DOM_FLAG and its corresponding metadata tables (change count, set time, clear time) 14 | - TRANSCEIVER_DOM_THRESHOLD 15 | """ 16 | TEMP_UNIT = 'C' 17 | VOLT_UNIT = 'Volts' 18 | POWER_UNIT = 'dBm' 19 | BIAS_UNIT = 'mA' 20 | 21 | def __init__(self, sfp_obj_dict, port_mapping, xcvr_table_helper, task_stopping_event, logger): 22 | super().__init__(sfp_obj_dict, port_mapping, task_stopping_event, logger) 23 | self.xcvr_table_helper = xcvr_table_helper 24 | self.dom_utils = DOMUtils(self.sfp_obj_dict, logger) 25 | self.logger = logger 26 | 27 | def post_port_dom_sensor_info_to_db(self, logical_port_name, db_cache=None): 28 | asic_index = self.port_mapping.get_asic_id_for_logical_port(logical_port_name) 29 | if asic_index is None: 30 | self.logger.log_error(f"Post port dom sensor info to db failed for {logical_port_name} " 31 | "as no asic index found") 32 | return 33 | 34 | return self.post_diagnostic_values_to_db(logical_port_name, 35 | self.xcvr_table_helper.get_dom_tbl(asic_index), 36 | self.dom_utils.get_transceiver_dom_sensor_real_value, 37 | db_cache=db_cache, 38 | beautify_func=self._beautify_dom_info_dict) 39 | 40 | def post_port_dom_flags_to_db(self, logical_port_name, db_cache=None): 41 | asic_index = self.port_mapping.get_asic_id_for_logical_port(logical_port_name) 42 | if asic_index is None: 43 | self.logger.log_error(f"Post port dom flags to db failed for {logical_port_name} " 44 | "as no asic index found") 45 | return 46 | 47 | physical_port = self._validate_and_get_physical_port(logical_port_name) 48 | if physical_port is None: 49 | return 50 | 51 | try: 52 | if db_cache is not None and physical_port in db_cache: 53 | # If cache is enabled and dom flag values are in cache, just read from cache, no need read from EEPROM 54 | dom_flags_dict = db_cache[physical_port] 55 | else: 56 | # Reading from the EEPROM as the cache is empty 57 | dom_flags_dict = self.dom_utils.get_transceiver_dom_flags(physical_port) 58 | if dom_flags_dict is None: 59 | self.logger.log_error(f"Post port dom flags to db failed for {logical_port_name} " 60 | "as no dom flags found") 61 | return 62 | if dom_flags_dict: 63 | dom_flags_dict_update_time = self.get_current_time() 64 | self._update_flag_metadata_tables(logical_port_name, dom_flags_dict, 65 | dom_flags_dict_update_time, 66 | self.xcvr_table_helper.get_dom_flag_tbl(asic_index), 67 | self.xcvr_table_helper.get_dom_flag_change_count_tbl(asic_index), 68 | self.xcvr_table_helper.get_dom_flag_set_time_tbl(asic_index), 69 | self.xcvr_table_helper.get_dom_flag_clear_time_tbl(asic_index), 70 | "DOM flags") 71 | 72 | if db_cache is not None: 73 | # If cache is enabled, put dom flag values to cache 74 | db_cache[physical_port] = dom_flags_dict 75 | 76 | if dom_flags_dict is not None: 77 | if not dom_flags_dict: 78 | return 79 | 80 | self._beautify_dom_info_dict(dom_flags_dict) 81 | fvs = swsscommon.FieldValuePairs( 82 | [(k, v) for k, v in dom_flags_dict.items()] + 83 | [("last_update_time", self.get_current_time())] 84 | ) 85 | self.xcvr_table_helper.get_dom_flag_tbl(asic_index).set(logical_port_name, fvs) 86 | else: 87 | return 88 | 89 | except NotImplementedError: 90 | self.logger.log_error(f"Post port dom flags to db failed for {logical_port_name} " 91 | "as no dom flags found") 92 | return 93 | 94 | def post_port_dom_thresholds_to_db(self, logical_port_name, db_cache=None): 95 | asic_index = self.port_mapping.get_asic_id_for_logical_port(logical_port_name) 96 | if asic_index is None: 97 | self.logger.log_error(f"Post port dom thresholds to db failed for {logical_port_name} " 98 | "as no asic index found") 99 | return 100 | 101 | return self.post_diagnostic_values_to_db(logical_port_name, 102 | self.xcvr_table_helper.get_dom_threshold_tbl(asic_index), 103 | self.dom_utils.get_transceiver_dom_thresholds, 104 | db_cache=db_cache, 105 | beautify_func=self._beautify_dom_info_dict) 106 | 107 | def _strip_unit(self, value, unit): 108 | # Strip unit from raw data 109 | if isinstance(value, str) and value.endswith(unit): 110 | return value[:-len(unit)] 111 | return str(value) 112 | 113 | # Remove unnecessary unit from the raw data 114 | def _beautify_dom_info_dict(self, dom_info_dict): 115 | if dom_info_dict is None: 116 | self.logger.log_warning("DOM info dict is None while beautifying") 117 | return 118 | 119 | for k, v in dom_info_dict.items(): 120 | if k == 'temperature': 121 | dom_info_dict[k] = self._strip_unit(v, self.TEMP_UNIT) 122 | elif k == 'voltage': 123 | dom_info_dict[k] = self._strip_unit(v, self.VOLT_UNIT) 124 | elif re.match('^(tx|rx)[1-8]power$', k): 125 | dom_info_dict[k] = self._strip_unit(v, self.POWER_UNIT) 126 | elif re.match('^(tx|rx)[1-8]bias$', k): 127 | dom_info_dict[k] = self._strip_unit(v, self.BIAS_UNIT) 128 | elif type(v) is not str: 129 | # For all the other keys: 130 | dom_info_dict[k] = str(v) 131 | -------------------------------------------------------------------------------- /sonic-xcvrd/xcvrd/dom/utilities/dom_sensor/utils.py: -------------------------------------------------------------------------------- 1 | class DOMUtils: 2 | """ 3 | This class provides utility functions for managing DOM operations on transceivers 4 | and call the corresponding methods in the SFP object. 5 | """ 6 | def __init__(self, sfp_obj_dict, logger): 7 | self.sfp_obj_dict = sfp_obj_dict 8 | self.logger = logger 9 | 10 | def get_transceiver_dom_sensor_real_value(self, physical_port): 11 | try: 12 | return self.sfp_obj_dict[physical_port].get_transceiver_dom_real_value() 13 | except (NotImplementedError): 14 | return {} 15 | 16 | def get_transceiver_dom_flags(self, physical_port): 17 | try: 18 | return self.sfp_obj_dict[physical_port].get_transceiver_dom_flags() 19 | except (NotImplementedError): 20 | return {} 21 | 22 | def get_transceiver_dom_thresholds(self, physical_port): 23 | try: 24 | return self.sfp_obj_dict[physical_port].get_transceiver_threshold_info() 25 | except (NotImplementedError): 26 | return {} 27 | -------------------------------------------------------------------------------- /sonic-xcvrd/xcvrd/dom/utilities/status/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-xcvrd/xcvrd/dom/utilities/status/__init__.py -------------------------------------------------------------------------------- /sonic-xcvrd/xcvrd/dom/utilities/status/db_utils.py: -------------------------------------------------------------------------------- 1 | from xcvrd.dom.utilities.db.utils import DBUtils 2 | from xcvrd.dom.utilities.status.utils import StatusUtils 3 | from swsscommon import swsscommon 4 | 5 | 6 | class StatusDBUtils(DBUtils): 7 | """ 8 | This class provides utility functions for managing DB operations 9 | related to transceiver status (specifically, all the hardware related fields of transceiver status). 10 | Handles data related to the following tables: 11 | - TRANSCEIVER_STATUS 12 | - TRANSCEIVER_STATUS_FLAG and its corresponding metadata tables (change count, set time, clear time) 13 | """ 14 | 15 | def __init__(self, sfp_obj_dict, port_mapping, xcvr_table_helper, task_stopping_event, logger): 16 | super().__init__(sfp_obj_dict, port_mapping, task_stopping_event, logger) 17 | self.xcvr_table_helper = xcvr_table_helper 18 | self.status_utils = StatusUtils(self.sfp_obj_dict, logger) 19 | self.logger = logger 20 | 21 | def post_port_transceiver_hw_status_to_db(self, logical_port_name, db_cache=None): 22 | """ 23 | Posts the hardware status of a transceiver to the database. 24 | 25 | Args: 26 | logical_port_name (str): Logical port name. 27 | db_cache (dict, optional): Cache for storing transceiver hardware status. 28 | 29 | """ 30 | asic_index = self.port_mapping.get_asic_id_for_logical_port(logical_port_name) 31 | if asic_index is None: 32 | self.logger.log_error(f"Post port transceiver hw status to db failed for {logical_port_name} " 33 | "as no asic index found") 34 | return 35 | 36 | return self.post_diagnostic_values_to_db(logical_port_name, 37 | self.xcvr_table_helper.get_status_tbl(asic_index), 38 | self.status_utils.get_transceiver_status, 39 | db_cache=db_cache) 40 | 41 | def post_port_transceiver_hw_status_flags_to_db(self, logical_port_name, db_cache=None): 42 | asic_index = self.port_mapping.get_asic_id_for_logical_port(logical_port_name) 43 | if asic_index is None: 44 | self.logger.log_error(f"Post port transceiver hw status flags to db failed for {logical_port_name} " 45 | "as no asic index found") 46 | return 47 | 48 | physical_port = self._validate_and_get_physical_port(logical_port_name) 49 | if physical_port is None: 50 | return 51 | 52 | try: 53 | if db_cache is not None and physical_port in db_cache: 54 | # If cache is enabled and status flag values are in cache, just read from cache, no need read from EEPROM 55 | status_flags_dict = db_cache[physical_port] 56 | else: 57 | # Reading from the EEPROM as the cache is empty 58 | status_flags_dict = self.status_utils.get_transceiver_status_flags(physical_port) 59 | if status_flags_dict is None: 60 | self.logger.log_error(f"Post port transceiver hw status flags to db failed for {logical_port_name} " 61 | "as no status flags found") 62 | return 63 | if status_flags_dict: 64 | self._update_flag_metadata_tables(logical_port_name, status_flags_dict, 65 | self.get_current_time(), 66 | self.xcvr_table_helper.get_status_flag_tbl(asic_index), 67 | self.xcvr_table_helper.get_status_flag_change_count_tbl(asic_index), 68 | self.xcvr_table_helper.get_status_flag_set_time_tbl(asic_index), 69 | self.xcvr_table_helper.get_status_flag_clear_time_tbl(asic_index), 70 | "Status flags") 71 | 72 | if db_cache is not None: 73 | # If cache is enabled, put status flag values to cache 74 | db_cache[physical_port] = status_flags_dict 75 | if status_flags_dict is not None: 76 | if not status_flags_dict: 77 | return 78 | 79 | self.beautify_info_dict(status_flags_dict) 80 | fvs = swsscommon.FieldValuePairs( 81 | [(k, v) for k, v in status_flags_dict.items()] + 82 | [("last_update_time", self.get_current_time())] 83 | ) 84 | self.xcvr_table_helper.get_status_flag_tbl(asic_index).set(logical_port_name, fvs) 85 | else: 86 | return 87 | 88 | except NotImplementedError: 89 | self.logger.log_notice(f"Post port transceiver hw status flags to db failed for {logical_port_name} " 90 | "as functionality is not implemented") 91 | return 92 | -------------------------------------------------------------------------------- /sonic-xcvrd/xcvrd/dom/utilities/status/utils.py: -------------------------------------------------------------------------------- 1 | class StatusUtils: 2 | """ 3 | This class provides utility functions for managing transceiver status operations on transceivers 4 | and call the corresponding methods in the SFP object. 5 | """ 6 | def __init__(self, sfp_obj_dict, logger): 7 | self.sfp_obj_dict = sfp_obj_dict 8 | self.logger = logger 9 | 10 | def get_transceiver_status(self, physical_port): 11 | """ 12 | Get the transceiver status for the given physical port. 13 | """ 14 | try: 15 | return self.sfp_obj_dict[physical_port].get_transceiver_status() 16 | except (NotImplementedError): 17 | return {} 18 | 19 | def get_transceiver_status_flags(self, physical_port): 20 | """ 21 | Get the transceiver status flags for the given physical port. 22 | """ 23 | try: 24 | return self.sfp_obj_dict[physical_port].get_transceiver_status_flags() 25 | except (NotImplementedError): 26 | return {} 27 | -------------------------------------------------------------------------------- /sonic-xcvrd/xcvrd/dom/utilities/vdm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-xcvrd/xcvrd/dom/utilities/vdm/__init__.py -------------------------------------------------------------------------------- /sonic-xcvrd/xcvrd/dom/utilities/vdm/db_utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from xcvrd.xcvrd_utilities.utils import XCVRDUtils 3 | from xcvrd.xcvrd_utilities.xcvr_table_helper import VDM_THRESHOLD_TYPES 4 | from xcvrd.dom.utilities.db.utils import DBUtils 5 | from xcvrd.dom.utilities.vdm.utils import VDMUtils 6 | from swsscommon import swsscommon 7 | 8 | class VDMDBUtils(DBUtils): 9 | """ 10 | This class provides utility functions for managing 11 | DB operations related to VDM on transceivers. 12 | Handles data related to the following tables: 13 | - TRANSCEIVER_VDM_REAL_VALUE 14 | - TRANSCEIVER_VDM_XXXX_FLAG and its corresponding metadata tables (change count, set time, clear time) 15 | - XXXX refers to HALARM, LALARM, HWARN or LWARN 16 | - TRANSCEIVER_VDM_XXXX_THRESHOLD 17 | - XXXX refers to HALARM, LALARM, HWARN or LWARN 18 | """ 19 | def __init__(self, sfp_obj_dict, port_mapping, xcvr_table_helper, task_stopping_event, logger): 20 | super().__init__(sfp_obj_dict, port_mapping, task_stopping_event, logger) 21 | self.xcvr_table_helper = xcvr_table_helper 22 | self.vdm_utils = VDMUtils(self.sfp_obj_dict, logger) 23 | self.logger = logger 24 | 25 | def post_port_vdm_real_values_to_db(self, logical_port_name, db_cache=None): 26 | asic_index = self.port_mapping.get_asic_id_for_logical_port(logical_port_name) 27 | if asic_index is None: 28 | self.logger.log_error(f"Post port vdm real values to db failed for {logical_port_name} " 29 | "as no asic index found") 30 | return 31 | 32 | return self.post_diagnostic_values_to_db(logical_port_name, 33 | self.xcvr_table_helper.get_vdm_real_value_tbl(asic_index), 34 | self.vdm_utils.get_vdm_real_values, 35 | db_cache=db_cache, 36 | enable_flat_memory_check=True) 37 | 38 | def post_port_vdm_flags_to_db(self, logical_port_name, db_cache=None): 39 | return self._post_port_vdm_thresholds_or_flags_to_db(logical_port_name, self.xcvr_table_helper.get_vdm_flag_tbl, 40 | self.vdm_utils.get_vdm_flags, flag_data=True, db_cache=db_cache) 41 | 42 | def post_port_vdm_thresholds_to_db(self, logical_port_name, db_cache=None): 43 | return self._post_port_vdm_thresholds_or_flags_to_db(logical_port_name, self.xcvr_table_helper.get_vdm_threshold_tbl, 44 | self.vdm_utils.get_vdm_thresholds, flag_data=False, db_cache=db_cache) 45 | 46 | # Update transceiver VDM threshold or flag info to db 47 | def _post_port_vdm_thresholds_or_flags_to_db(self, logical_port_name, get_vdm_table_func, 48 | get_vdm_values_func, flag_data=False, db_cache=None): 49 | physical_port = self._validate_and_get_physical_port(logical_port_name, enable_flat_memory_check=True) 50 | if physical_port is None: 51 | return 52 | 53 | try: 54 | if db_cache is not None and physical_port in db_cache: 55 | vdm_threshold_type_value_dict = db_cache[physical_port] 56 | else: 57 | # Reading from the EEPROM as the cache is empty 58 | # The vdm_values_dict contains the threshold type in the key for all the VDM observable types 59 | vdm_values_dict = get_vdm_values_func(physical_port) 60 | if vdm_values_dict is None: 61 | self.logger.log_error(f"Post port vdm thresholds or flags to db failed for {logical_port_name} " 62 | "as no vdm values found with flag_data {flag_data}") 63 | return 64 | vdm_values_dict_update_time = self.get_current_time() 65 | # Creating a dict with the threshold type as the key 66 | # This is done so that a separate redis-db table is created for each threshold type 67 | vdm_threshold_type_value_dict = {threshold_type: {} for threshold_type in VDM_THRESHOLD_TYPES} 68 | for key, value in vdm_values_dict.items(): 69 | for threshold_type in VDM_THRESHOLD_TYPES: 70 | if f'_{threshold_type}' in key: 71 | # The vdm_values_dict contains the threshold type in the key. Hence, remove the 72 | # threshold type from the key since the tables are already separated by threshold type 73 | new_key = key.replace(f'_{threshold_type}', '') 74 | vdm_threshold_type_value_dict[threshold_type][new_key] = value 75 | 76 | for threshold_type, threshold_value_dict in vdm_threshold_type_value_dict.items(): 77 | # If the current update is a flag update, then update the metadata tables 78 | # for the flags 79 | if flag_data and threshold_value_dict: 80 | asic_id = self.port_mapping.get_asic_id_for_logical_port(logical_port_name) 81 | self._update_flag_metadata_tables(logical_port_name, threshold_value_dict, 82 | vdm_values_dict_update_time, 83 | self.xcvr_table_helper.get_vdm_flag_tbl(asic_id, threshold_type), 84 | self.xcvr_table_helper.get_vdm_flag_change_count_tbl(asic_id, threshold_type), 85 | self.xcvr_table_helper.get_vdm_flag_set_time_tbl(asic_id, threshold_type), 86 | self.xcvr_table_helper.get_vdm_flag_clear_time_tbl(asic_id, threshold_type), 87 | f"VDM {threshold_type}") 88 | 89 | if db_cache is not None: 90 | # If cache is enabled, put vdm values to cache 91 | # VDM metadata tables are stored only in one of the logical ports for a port breakout group. This 92 | # is done since the tables are planned to be created only for one of the logical ports for a port breakout group in future. 93 | db_cache[physical_port] = vdm_threshold_type_value_dict 94 | 95 | for threshold_type, threshold_value_dict in vdm_threshold_type_value_dict.items(): 96 | if threshold_value_dict: 97 | self.beautify_info_dict(threshold_value_dict) 98 | fvs = swsscommon.FieldValuePairs( 99 | [(k, v) for k, v in threshold_value_dict.items()] + 100 | [("last_update_time", self.get_current_time())] 101 | ) 102 | 103 | table = get_vdm_table_func(self.port_mapping.get_asic_id_for_logical_port(logical_port_name), threshold_type) 104 | table.set(logical_port_name, fvs) 105 | else: 106 | return 107 | except NotImplementedError: 108 | self.logger.log_error(f"Post port vdm thresholds or flags to db failed for {logical_port_name} " 109 | "as functionality is not implemented with flag_data {flag_data}") 110 | return 111 | -------------------------------------------------------------------------------- /sonic-xcvrd/xcvrd/dom/utilities/vdm/utils.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | import time 3 | 4 | MAX_tVDMF_TIME_MSECS = 10 5 | MAX_VDM_FREEZE_UNFREEZE_TIME_MSECS = 1000 6 | FREEZE_UNFREEZE_DONE_POLLING_INTERVAL_MSECS = 1 7 | 8 | class VDMUtils: 9 | """ 10 | This class provides utility functions for managing VDM operations on transceivers 11 | and call the corresponding methods in the SFP object. 12 | """ 13 | def __init__(self, sfp_obj_dict, logger): 14 | self.sfp_obj_dict = sfp_obj_dict 15 | self.logger = logger 16 | 17 | def is_transceiver_vdm_supported(self, physical_port): 18 | try: 19 | return self.sfp_obj_dict[physical_port].is_transceiver_vdm_supported() 20 | except (NotImplementedError): 21 | return False 22 | 23 | def get_vdm_real_values(self, physical_port): 24 | try: 25 | return self.sfp_obj_dict[physical_port].get_transceiver_vdm_real_value() 26 | except (NotImplementedError): 27 | self.logger.log_error(f"Failed to get VDM real values for port {physical_port}") 28 | return {} 29 | 30 | def get_vdm_flags(self, physical_port): 31 | try: 32 | return self.sfp_obj_dict[physical_port].get_transceiver_vdm_flags() 33 | except (NotImplementedError): 34 | self.logger.log_error(f"Failed to get VDM flags for port {physical_port}") 35 | return {} 36 | 37 | def get_vdm_thresholds(self, physical_port): 38 | try: 39 | return self.sfp_obj_dict[physical_port].get_transceiver_vdm_thresholds() 40 | except (NotImplementedError): 41 | return {} 42 | 43 | @contextmanager 44 | def vdm_freeze_context(self, physical_port): 45 | try: 46 | if not self._freeze_vdm_stats_and_confirm(physical_port): 47 | self.logger.log_error(f"Failed to freeze VDM stats in contextmanager for port {physical_port}") 48 | yield False 49 | else: 50 | yield True 51 | finally: 52 | if not self._unfreeze_vdm_stats_and_confirm(physical_port): 53 | self.logger.log_error(f"Failed to unfreeze VDM stats in contextmanager for port {physical_port}") 54 | 55 | def _vdm_action_and_confirm(self, physical_port, action, status_check, action_name): 56 | """ 57 | Helper function to perform VDM action (freeze/unfreeze) and confirm the status. 58 | Args: 59 | physical_port: The physical port index. 60 | action: The action to perform (freeze/unfreeze). 61 | status_check: The function to check the status. 62 | action_name: The name of the action for logging purposes. 63 | Returns: 64 | True if the action is successful, False otherwise. 65 | """ 66 | try: 67 | status = action() 68 | if not status: 69 | self.logger.log_error(f"Failed to {action_name} VDM stats for port {physical_port}") 70 | return False 71 | 72 | # Wait for MAX_tVDMF_TIME_MSECS to allow the module to clear the done bit 73 | time.sleep(MAX_tVDMF_TIME_MSECS / 1000) 74 | 75 | # Poll for the done bit to be set 76 | start_time = time.time() 77 | while time.time() - start_time < MAX_VDM_FREEZE_UNFREEZE_TIME_MSECS / 1000: 78 | if status_check(): 79 | return True 80 | time.sleep(FREEZE_UNFREEZE_DONE_POLLING_INTERVAL_MSECS / 1000) 81 | 82 | self.logger.log_error(f"Failed to confirm VDM {action_name} status for port {physical_port}") 83 | except (KeyError, NotImplementedError) as e: 84 | # Handle the case where the SFP object does not exist or the method is not implemented 85 | self.logger.log_error(f"VDM {action_name} failed for port {physical_port} with exception {e}") 86 | return False 87 | 88 | return False 89 | 90 | def _freeze_vdm_stats_and_confirm(self, physical_port): 91 | """ 92 | Freezes and confirms the VDM freeze status of the transceiver. 93 | Args: 94 | physical_port: The physical port index. 95 | Returns: 96 | True if the VDM stats are frozen successfully, False otherwise. 97 | """ 98 | sfp = self.sfp_obj_dict.get(physical_port) 99 | if not sfp: 100 | self.logger.log_error(f"Freeze VDM stats failed: {physical_port} not found in sfp_obj_dict") 101 | return False 102 | 103 | return self._vdm_action_and_confirm(physical_port, sfp.freeze_vdm_stats, 104 | sfp.get_vdm_freeze_status, "freeze") 105 | 106 | def _unfreeze_vdm_stats_and_confirm(self, physical_port): 107 | """ 108 | Unfreezes and confirms the VDM unfreeze status of the transceiver. 109 | Args: 110 | physical_port: The physical port index. 111 | Returns: 112 | True if the VDM stats are unfrozen successfully, False otherwise. 113 | """ 114 | sfp = self.sfp_obj_dict.get(physical_port) 115 | if not sfp: 116 | self.logger.log_error(f"Unfreeze VDM stats failed: {physical_port} not found in sfp_obj_dict") 117 | return False 118 | 119 | return self._vdm_action_and_confirm(physical_port, sfp.unfreeze_vdm_stats, 120 | sfp.get_vdm_unfreeze_status, "unfreeze") 121 | -------------------------------------------------------------------------------- /sonic-xcvrd/xcvrd/xcvrd_utilities/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-xcvrd/xcvrd/xcvrd_utilities/__init__.py -------------------------------------------------------------------------------- /sonic-xcvrd/xcvrd/xcvrd_utilities/optics_si_parser.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import re 4 | 5 | from sonic_py_common import device_info, syslogger 6 | from xcvrd import xcvrd 7 | from . import common 8 | 9 | g_optics_si_dict = {} 10 | 11 | SYSLOG_IDENTIFIER = "xcvrd" 12 | helper_logger = syslogger.SysLogger(SYSLOG_IDENTIFIER, enable_runtime_config=True) 13 | 14 | def _match_optics_si_key(dict_key, key, vendor_name_str): 15 | """ 16 | Helper function to match optics SI key using regex patterns or string comparison 17 | 18 | Args: 19 | dict_key: Key from optics SI settings (can be regex pattern) 20 | key: Vendor key to match (e.g., "ABCDE-1234") 21 | vendor_name_str: Vendor name string (e.g., "ABCDE") 22 | 23 | Returns: 24 | True if match found, False otherwise 25 | """ 26 | try: 27 | # Use re.fullmatch to match the entire string with regex patterns 28 | # This supports patterns like "ABCDE-(1234|56789)" 29 | if (re.fullmatch(dict_key, key) or \ 30 | re.fullmatch(dict_key, key.split('-')[0]) or \ 31 | re.fullmatch(dict_key, vendor_name_str)): 32 | return True 33 | except re.error: 34 | # If regex pattern is invalid, fall back to simple string comparison 35 | if (dict_key == key or \ 36 | dict_key == key.split('-')[0] or \ 37 | dict_key == vendor_name_str): 38 | return True 39 | return False 40 | 41 | def _get_global_media_settings(physical_port, lane_speed, key, vendor_name_str): 42 | """ 43 | Get optics SI settings from global media settings 44 | 45 | Args: 46 | physical_port: Physical port number 47 | lane_speed: Lane speed value 48 | key: Vendor key to match 49 | vendor_name_str: Vendor name string 50 | 51 | Returns: 52 | Tuple of (settings_dict, default_dict) 53 | """ 54 | GLOBAL_MEDIA_SETTINGS_KEY = 'GLOBAL_MEDIA_SETTINGS' 55 | DEFAULT_KEY = 'Default' 56 | SPEED_KEY = str(lane_speed) + 'G_SPEED' 57 | RANGE_SEPARATOR = '-' 58 | COMMA_SEPARATOR = ',' 59 | default_dict = {} 60 | optics_si_dict = {} 61 | 62 | if GLOBAL_MEDIA_SETTINGS_KEY in g_optics_si_dict: 63 | for keys in g_optics_si_dict[GLOBAL_MEDIA_SETTINGS_KEY]: 64 | if COMMA_SEPARATOR in keys: 65 | port_list = keys.split(COMMA_SEPARATOR) 66 | for port in port_list: 67 | if RANGE_SEPARATOR in port: 68 | if common.check_port_in_range(port, physical_port): 69 | optics_si_dict = g_optics_si_dict[GLOBAL_MEDIA_SETTINGS_KEY][keys] 70 | break 71 | elif str(physical_port) == port: 72 | optics_si_dict = g_optics_si_dict[GLOBAL_MEDIA_SETTINGS_KEY][keys] 73 | break 74 | 75 | elif RANGE_SEPARATOR in keys: 76 | if common.check_port_in_range(keys, physical_port): 77 | optics_si_dict = g_optics_si_dict[GLOBAL_MEDIA_SETTINGS_KEY][keys] 78 | 79 | if SPEED_KEY in optics_si_dict: 80 | # Iterate through each key in optics_si_dict[SPEED_KEY] and use regex matching 81 | for dict_key in optics_si_dict[SPEED_KEY].keys(): 82 | if _match_optics_si_key(dict_key, key, vendor_name_str): 83 | return optics_si_dict[SPEED_KEY][dict_key], default_dict 84 | 85 | # If no match found, try default 86 | if DEFAULT_KEY in optics_si_dict[SPEED_KEY]: 87 | default_dict = optics_si_dict[SPEED_KEY][DEFAULT_KEY] 88 | 89 | return None, default_dict 90 | 91 | def _get_port_media_settings(physical_port, lane_speed, key, vendor_name_str, default_dict): 92 | """ 93 | Get optics SI settings from port-specific media settings 94 | 95 | Args: 96 | physical_port: Physical port number 97 | lane_speed: Lane speed value 98 | key: Any key to match 99 | vendor_name_str: Vendor name string 100 | default_dict: Default settings from global media settings 101 | 102 | Returns: 103 | Settings dictionary or default_dict 104 | """ 105 | PORT_MEDIA_SETTINGS_KEY = 'PORT_MEDIA_SETTINGS' 106 | DEFAULT_KEY = 'Default' 107 | SPEED_KEY = str(lane_speed) + 'G_SPEED' 108 | optics_si_dict = {} 109 | 110 | if PORT_MEDIA_SETTINGS_KEY in g_optics_si_dict: 111 | for keys in g_optics_si_dict[PORT_MEDIA_SETTINGS_KEY]: 112 | if int(keys) == physical_port: 113 | optics_si_dict = g_optics_si_dict[PORT_MEDIA_SETTINGS_KEY][keys] 114 | break 115 | 116 | if len(optics_si_dict) == 0: 117 | if len(default_dict) != 0: 118 | return default_dict 119 | else: 120 | helper_logger.log_info("No values for physical port '{}' lane speed '{}' " 121 | "key '{}' vendor '{}'".format( 122 | physical_port, lane_speed, key, vendor_name_str)) 123 | return {} 124 | 125 | if SPEED_KEY in optics_si_dict: 126 | # Iterate through each key in optics_si_dict[SPEED_KEY] and use regex matching 127 | for dict_key in optics_si_dict[SPEED_KEY].keys(): 128 | if _match_optics_si_key(dict_key, key, vendor_name_str): 129 | return optics_si_dict[SPEED_KEY][dict_key] 130 | 131 | # If no match found, try default 132 | if DEFAULT_KEY in optics_si_dict[SPEED_KEY]: 133 | return optics_si_dict[SPEED_KEY][DEFAULT_KEY] 134 | elif len(default_dict) != 0: 135 | return default_dict 136 | 137 | return default_dict 138 | 139 | def get_optics_si_settings_value(physical_port, lane_speed, key, vendor_name_str): 140 | """ 141 | Get optics SI settings value for the given parameters 142 | 143 | Args: 144 | physical_port: Physical port number 145 | lane_speed: Lane speed value 146 | key: Any key to match 147 | vendor_name_str: Vendor name string 148 | 149 | Returns: 150 | Settings dictionary 151 | """ 152 | # Try to get settings from global media settings first 153 | global_settings, default_dict = _get_global_media_settings(physical_port, lane_speed, key, vendor_name_str) 154 | if global_settings is not None: 155 | return global_settings 156 | 157 | # If not found in global settings, try port-specific settings 158 | port_settings = _get_port_media_settings(physical_port, lane_speed, key, vendor_name_str, default_dict) 159 | return port_settings 160 | 161 | def get_module_vendor_key(physical_port, sfp): 162 | api = sfp.get_xcvr_api() 163 | if api is None: 164 | helper_logger.log_info("Module {} xcvrd api not found".format(physical_port)) 165 | return None 166 | 167 | vendor_name = api.get_manufacturer() 168 | if vendor_name is None: 169 | helper_logger.log_info("Module {} vendor name not found".format(physical_port)) 170 | return None 171 | 172 | vendor_pn = api.get_model() 173 | if vendor_pn is None: 174 | helper_logger.log_info("Module {} vendor part number not found".format(physical_port)) 175 | return None 176 | 177 | return vendor_name.upper().strip() + '-' + vendor_pn.upper().strip(), vendor_name.upper().strip() 178 | 179 | def fetch_optics_si_setting(physical_port, lane_speed, sfp): 180 | if not g_optics_si_dict: 181 | return 182 | 183 | optics_si = {} 184 | 185 | if not common._wrapper_get_presence(physical_port): 186 | helper_logger.log_info("Module {} presence not detected during notify".format(physical_port)) 187 | return optics_si 188 | vendor_key, vendor_name = get_module_vendor_key(physical_port, sfp) 189 | if vendor_key is None or vendor_name is None: 190 | helper_logger.log_error("Error: No Vendor Key found for Module {}".format(physical_port)) 191 | return optics_si 192 | optics_si = get_optics_si_settings_value(physical_port, lane_speed, vendor_key, vendor_name) 193 | return optics_si 194 | 195 | def load_optics_si_settings(): 196 | global g_optics_si_dict 197 | (platform_path, hwsku_path) = device_info.get_paths_to_platform_and_hwsku_dirs() 198 | 199 | # Support to fetch optics_si_settings.json both from platform folder and HWSKU folder 200 | optics_si_settings_file_path_platform = os.path.join(platform_path, "optics_si_settings.json") 201 | optics_si_settings_file_path_hwsku = os.path.join(hwsku_path, "optics_si_settings.json") 202 | 203 | if os.path.isfile(optics_si_settings_file_path_hwsku): 204 | optics_si_settings_file_path = optics_si_settings_file_path_hwsku 205 | elif os.path.isfile(optics_si_settings_file_path_platform): 206 | optics_si_settings_file_path = optics_si_settings_file_path_platform 207 | else: 208 | helper_logger.log_info("No optics SI file exists") 209 | return {} 210 | 211 | with open(optics_si_settings_file_path, "r") as optics_si_file: 212 | g_optics_si_dict = json.load(optics_si_file) 213 | 214 | def optics_si_present(): 215 | if g_optics_si_dict: 216 | return True 217 | return False 218 | 219 | -------------------------------------------------------------------------------- /sonic-xcvrd/xcvrd/xcvrd_utilities/sfp_status_helper.py: -------------------------------------------------------------------------------- 1 | from sonic_platform_base.sfp_base import SfpBase 2 | 3 | # SFP status definition, shall be aligned with the definition in get_change_event() of ChassisBase 4 | SFP_STATUS_REMOVED = '0' 5 | SFP_STATUS_INSERTED = '1' 6 | 7 | # SFP error code dictinary, new elements can be added if new errors need to be supported. 8 | SFP_ERRORS_BLOCKING_MASK = 0x02 9 | SFP_ERRORS_GENERIC_MASK = 0x0000FFFE 10 | SFP_ERRORS_VENDOR_SPECIFIC_MASK = 0xFFFF0000 11 | 12 | def is_error_block_eeprom_reading(error_bits): 13 | return 0 != (error_bits & SFP_ERRORS_BLOCKING_MASK) 14 | 15 | 16 | def has_vendor_specific_error(error_bits): 17 | return 0 != (error_bits & SFP_ERRORS_VENDOR_SPECIFIC_MASK) 18 | 19 | 20 | def fetch_generic_error_description(error_bits): 21 | generic_error_bits = (error_bits & SFP_ERRORS_GENERIC_MASK) 22 | error_descriptions = [] 23 | if generic_error_bits: 24 | for error_bit, error_description in SfpBase.SFP_ERROR_BIT_TO_DESCRIPTION_DICT.items(): 25 | if error_bit & generic_error_bits: 26 | error_descriptions.append(error_description) 27 | return error_descriptions 28 | 29 | 30 | def detect_port_in_error_status(logical_port_name, status_sw_tbl): 31 | rec, fvp = status_sw_tbl.get(logical_port_name) 32 | if rec: 33 | status_sw_dict = dict(fvp) 34 | error = status_sw_dict.get('error') 35 | if error is not None: 36 | return SfpBase.SFP_ERROR_DESCRIPTION_BLOCKING in error 37 | else: 38 | return False 39 | return False 40 | 41 | -------------------------------------------------------------------------------- /sonic-xcvrd/xcvrd/xcvrd_utilities/utils.py: -------------------------------------------------------------------------------- 1 | class XCVRDUtils: 2 | """ 3 | This class provides utility functions for managing XCVRD operations on transceivers 4 | and call the corresponding methods in the SFP object. 5 | """ 6 | def __init__(self, sfp_obj_dict, logger): 7 | self.sfp_obj_dict = sfp_obj_dict 8 | self.logger = logger 9 | 10 | def get_transceiver_presence(self, physical_port): 11 | try: 12 | return self.sfp_obj_dict[physical_port].get_presence() 13 | except (KeyError, NotImplementedError): 14 | self.logger.log_error(f"Failed to get presence for port {physical_port}") 15 | return False 16 | 17 | def is_transceiver_flat_memory(self, physical_port): 18 | try: 19 | api = self.sfp_obj_dict[physical_port].get_xcvr_api() 20 | if not api: 21 | return True 22 | return api.is_flat_memory() 23 | except (KeyError, NotImplementedError): 24 | self.logger.log_error(f"Failed to check flat memory for port {physical_port}") 25 | return True 26 | 27 | def is_transceiver_lpmode_on(self, physical_port): 28 | try: 29 | return self.sfp_obj_dict[physical_port].get_lpmode() 30 | except Exception as e: 31 | self.logger.log_error(f"Failed to get low power mode for port {physical_port}. Exception: {e}") 32 | return False 33 | -------------------------------------------------------------------------------- /sonic-ycabled/proto/proto_out/linkmgr_grpc_driver.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | service DualToRActive { 4 | rpc QueryAdminForwardingPortState(AdminRequest) returns (AdminReply) {} 5 | rpc SetAdminForwardingPortState(AdminRequest) returns (AdminReply) {} 6 | rpc QueryOperationPortState(OperationRequest) returns (OperationReply) {} 7 | rpc QueryLinkState(LinkStateRequest) returns (LinkStateReply) {} 8 | rpc QueryServerVersion(ServerVersionRequest) returns (ServerVersionReply) {} 9 | } 10 | 11 | message AdminRequest { 12 | repeated int32 portid = 1; 13 | repeated bool state = 2; 14 | } 15 | 16 | message AdminReply { 17 | repeated int32 portid = 1; 18 | repeated bool state = 2; 19 | } 20 | 21 | message OperationRequest { 22 | repeated int32 portid = 1; 23 | } 24 | 25 | message OperationReply { 26 | repeated int32 portid = 1; 27 | repeated bool state = 2; 28 | } 29 | 30 | message LinkStateRequest { 31 | repeated int32 portid = 1; 32 | } 33 | 34 | message LinkStateReply { 35 | repeated int32 portid = 1; 36 | repeated bool state = 2; 37 | } 38 | 39 | message ServerVersionRequest { 40 | string version = 1; 41 | } 42 | 43 | message ServerVersionReply { 44 | string version = 1; 45 | } 46 | 47 | 48 | service GracefulRestart { 49 | 50 | rpc NotifyGracefulRestartStart(GracefulAdminRequest) returns (stream GracefulAdminResponse) {} 51 | 52 | } 53 | 54 | enum ToRSide { 55 | LOWER_TOR = 0; 56 | UPPER_TOR =1; 57 | } 58 | 59 | message GracefulAdminRequest { 60 | ToRSide tor = 1; 61 | } 62 | 63 | enum GracefulRestartMsgType { 64 | SERVICE_BEGIN = 0; 65 | SERVICE_END = 1;// send this always from SoC Side even if not paired with Begin 66 | } 67 | 68 | enum GracefulRestartNotifyType { 69 | CONTROL_PLANE = 0;// need proper definitions 70 | DATA_PLANE = 1; 71 | BOTH = 2; 72 | } 73 | 74 | message GracefulAdminResponse { 75 | GracefulRestartMsgType msgtype = 1; 76 | GracefulRestartNotifyType notifytype = 2; 77 | string guid = 3; 78 | int32 period = 4; // in seconds 79 | } 80 | -------------------------------------------------------------------------------- /sonic-ycabled/proto_out/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-ycabled/proto_out/__init__.py -------------------------------------------------------------------------------- /sonic-ycabled/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --cov=ycable --cov-report html --cov-report term --cov-report xml --junitxml=test-results.xml -v 3 | -------------------------------------------------------------------------------- /sonic-ycabled/setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | -------------------------------------------------------------------------------- /sonic-ycabled/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from setuptools.command.build_py import build_py as _build_py 3 | import distutils.command 4 | import os.path 5 | import sys 6 | 7 | class GrpcTool(distutils.cmd.Command): 8 | def initialize_options(self): 9 | pass 10 | 11 | def finalize_options(self): 12 | pass 13 | 14 | def run(self): 15 | import grpc_tools.protoc 16 | 17 | grpc_tools.protoc.main([ 18 | 'grpc_tools.protoc', 19 | '-Iproto', 20 | '--python_out=.', 21 | '--grpc_python_out=.', 22 | 'proto/proto_out/linkmgr_grpc_driver.proto' 23 | ]) 24 | 25 | class BuildPyCommand(_build_py, object): 26 | 27 | # When 'python3 -m build -n' is executed, by default 'sdist' command 28 | # is executed and it copies .py and other default files to separate 29 | # dir and generates a sdist of it, from which a wheel is created. 30 | # Hence, generate the required python files in initialization of 31 | # 'build_py' itself to make it available for other commands. 32 | def initialize_options(self): 33 | # .proto files are not copied by 'sdist' command. 34 | # So, execute GrpcTool only if the proto dir is present. 35 | if os.path.exists('proto'): 36 | self.run_command('GrpcTool') 37 | 38 | proto_py_files = ['proto_out/linkmgr_grpc_driver_pb2.py', 'proto_out/linkmgr_grpc_driver_pb2_grpc.py'] 39 | for py_file in proto_py_files: 40 | if not os.path.exists(py_file): 41 | print('Required file not present: {0}'.format(py_file)) 42 | sys.exit(1) 43 | 44 | super(BuildPyCommand, self).initialize_options() 45 | 46 | setup( 47 | name='sonic-ycabled', 48 | version='1.0', 49 | description='Y-cable and smart nic configuration daemon for SONiC', 50 | license='Apache 2.0', 51 | author='SONiC Team', 52 | author_email='linuxnetdev@microsoft.com', 53 | url='https://github.com/Azure/sonic-platform-daemons', 54 | maintainer='Vaibhav Dahiya', 55 | maintainer_email='vdahiya@microsoft.com', 56 | packages=find_packages(), 57 | entry_points={ 58 | 'console_scripts': [ 59 | 'ycabled = ycable.ycable:main', 60 | ] 61 | }, 62 | cmdclass={ 63 | 'build_py': BuildPyCommand, 64 | 'GrpcTool': GrpcTool 65 | }, 66 | install_requires=[ 67 | # NOTE: This package also requires swsscommon, but it is not currently installed as a wheel 68 | 'enum34; python_version < "3.4"', 69 | 'sonic-py-common', 70 | ], 71 | setup_requires=[ 72 | 'wheel', 73 | 'grpcio-tools' 74 | ], 75 | tests_require=[ 76 | 'pytest', 77 | 'pytest-cov', 78 | ], 79 | classifiers=[ 80 | 'Development Status :: 4 - Beta', 81 | 'Environment :: No Input/Output (Daemon)', 82 | 'Intended Audience :: Developers', 83 | 'Intended Audience :: Information Technology', 84 | 'Intended Audience :: System Administrators', 85 | 'License :: OSI Approved :: Apache Software License', 86 | 'Natural Language :: English', 87 | 'Operating System :: POSIX :: Linux', 88 | 'Programming Language :: Python :: 3.7', 89 | 'Topic :: System :: Hardware', 90 | ], 91 | keywords='sonic SONiC TRANSCEIVER transceiver daemon YCABLE ycable', 92 | ) 93 | -------------------------------------------------------------------------------- /sonic-ycabled/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-ycabled/tests/__init__.py -------------------------------------------------------------------------------- /sonic-ycabled/tests/mock_swsscommon.py: -------------------------------------------------------------------------------- 1 | STATE_DB = '' 2 | CHASSIS_STATE_DB = '' 3 | 4 | 5 | class Table: 6 | def __init__(self, db, table_name): 7 | self.table_name = table_name 8 | self.mock_dict = {} 9 | 10 | def _del(self, key): 11 | if key in self.mock_dict: 12 | del self.mock_dict[key] 13 | pass 14 | 15 | def set(self, key, fvs): 16 | self.mock_dict[key] = fvs 17 | pass 18 | 19 | def get(self, key): 20 | if key in self.mock_dict: 21 | return self.mock_dict[key] 22 | return None 23 | 24 | def get_size(self): 25 | return (len(self.mock_dict)) 26 | 27 | 28 | class FieldValuePairs: 29 | def __init__(self, fvs): 30 | self.fv_dict = dict(fvs) 31 | pass 32 | -------------------------------------------------------------------------------- /sonic-ycabled/ycable/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-ycabled/ycable/__init__.py -------------------------------------------------------------------------------- /sonic-ycabled/ycable/ycable_utilities/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonic-net/sonic-platform-daemons/99a45940ff92b2513f038c7bf727795b80252e7e/sonic-ycabled/ycable/ycable_utilities/__init__.py --------------------------------------------------------------------------------