From d7b96dfdf1a241035a372326c5c8e4769efe4a9f Mon Sep 17 00:00:00 2001 From: Stepan Blyshchak <38952541+stepanblyschak@users.noreply.github.com> Date: Fri, 28 May 2021 20:16:02 +0300 Subject: [PATCH 1/8] [sonic-sdk] add sonic sdk and sonic sdk buildenv (#6712) - Why I did it To give SONiC Application Extension developers an environment to run and develop their apps. - How I did it Created sonic-sdk and sonic-sdk-buildenv dockers and their dbg versions. - How to verify it Build: $ make -f slave target/sonic-sdk.gz target/sonic-sdk-buildenv.gz --- build_debian.sh | 19 ++++---- .../docker-sonic-sdk-buildenv/Dockerfile.j2 | 43 +++++++++++++++++++ dockers/docker-sonic-sdk/Dockerfile.j2 | 29 +++++++++++++ files/build_templates/sonic_version.yml.j2 | 20 +++++++++ .../vs/sonic-version/build_sonic_version.sh | 15 +++---- .../vs/sonic-version/sonic_version.yml.j2 | 1 + rules/docker-config-engine-buster.mk | 4 +- rules/docker-sonic-sdk-buildenv.dep | 12 ++++++ rules/docker-sonic-sdk-buildenv.mk | 29 +++++++++++++ rules/docker-sonic-sdk.dep | 12 ++++++ rules/docker-sonic-sdk.mk | 24 +++++++++++ rules/functions | 2 + rules/sairedis.mk | 23 ++++++---- rules/sonic-utilities.mk | 6 ++- rules/swss-common.mk | 15 ++++--- slave.mk | 12 +++++- 16 files changed, 230 insertions(+), 36 deletions(-) create mode 100755 dockers/docker-sonic-sdk-buildenv/Dockerfile.j2 create mode 100755 dockers/docker-sonic-sdk/Dockerfile.j2 create mode 100644 files/build_templates/sonic_version.yml.j2 create mode 120000 platform/vs/sonic-version/sonic_version.yml.j2 create mode 100644 rules/docker-sonic-sdk-buildenv.dep create mode 100644 rules/docker-sonic-sdk-buildenv.mk create mode 100644 rules/docker-sonic-sdk.dep create mode 100644 rules/docker-sonic-sdk.mk diff --git a/build_debian.sh b/build_debian.sh index c3e8e945443..7c2770cc73e 100755 --- a/build_debian.sh +++ b/build_debian.sh @@ -474,16 +474,15 @@ fi ## Version file sudo mkdir -p $FILESYSTEM_ROOT/etc/sonic -sudo tee $FILESYSTEM_ROOT/etc/sonic/sonic_version.yml > /dev/null < /dev/null < $1 diff --git a/platform/vs/sonic-version/sonic_version.yml.j2 b/platform/vs/sonic-version/sonic_version.yml.j2 new file mode 120000 index 00000000000..2bbebeb2254 --- /dev/null +++ b/platform/vs/sonic-version/sonic_version.yml.j2 @@ -0,0 +1 @@ +../../../files/build_templates/sonic_version.yml.j2 \ No newline at end of file diff --git a/rules/docker-config-engine-buster.mk b/rules/docker-config-engine-buster.mk index b386c882f4c..f7232b23d64 100644 --- a/rules/docker-config-engine-buster.mk +++ b/rules/docker-config-engine-buster.mk @@ -11,7 +11,9 @@ $(DOCKER_CONFIG_ENGINE_BUSTER)_LOAD_DOCKERS += $(DOCKER_BASE_BUSTER) $(DOCKER_CONFIG_ENGINE_BUSTER)_FILES += $(SWSS_VARS_TEMPLATE) $(DOCKER_CONFIG_ENGINE_BUSTER)_FILES += $($(SONIC_CTRMGRD)_CONTAINER_SCRIPT) -$(DOCKER_CONFIG_ENGINE_BUSTER)_DBG_DEPENDS = $($(DOCKER_BASE_BUSTER)_DBG_DEPENDS) +$(DOCKER_CONFIG_ENGINE_BUSTER)_DBG_DEPENDS = $($(DOCKER_BASE_BUSTER)_DBG_DEPENDS) \ + $(LIBSWSSCOMMON_DBG) \ + $(LIBHIREDIS_DBG) $(DOCKER_CONFIG_ENGINE_BUSTER)_DBG_IMAGE_PACKAGES = $($(DOCKER_BASE_BUSTER)_DBG_IMAGE_PACKAGES) SONIC_DOCKER_IMAGES += $(DOCKER_CONFIG_ENGINE_BUSTER) diff --git a/rules/docker-sonic-sdk-buildenv.dep b/rules/docker-sonic-sdk-buildenv.dep new file mode 100644 index 00000000000..56f1c0ff7ab --- /dev/null +++ b/rules/docker-sonic-sdk-buildenv.dep @@ -0,0 +1,12 @@ + +DPATH := $($(DOCKER_SONIC_SDK_BUILDENV)_PATH) +DEP_FILES := $(SONIC_COMMON_FILES_LIST) rules/docker-sonic-sdk-buildenv.mk rules/docker-sonic-sdk-buildenv.dep +DEP_FILES += $(SONIC_COMMON_BASE_FILES_LIST) +DEP_FILES += $(shell git ls-files $(DPATH)) + +$(DOCKER_SONIC_SDK_BUILDENV)_CACHE_MODE := GIT_CONTENT_SHA +$(DOCKER_SONIC_SDK_BUILDENV)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST) +$(DOCKER_SONIC_SDK_BUILDENV)_DEP_FILES := $(DEP_FILES) + +$(eval $(call add_dbg_docker,$(DOCKER_SONIC_SDK_BUILDENV),$(DOCKER_SONIC_SDK_BUILDENV_DBG))) + diff --git a/rules/docker-sonic-sdk-buildenv.mk b/rules/docker-sonic-sdk-buildenv.mk new file mode 100644 index 00000000000..52e11b815cf --- /dev/null +++ b/rules/docker-sonic-sdk-buildenv.mk @@ -0,0 +1,29 @@ +# SONiC SDK Docker Image + +DOCKER_SONIC_SDK_BUILDENV_STEM = sonic-sdk-buildenv +DOCKER_SONIC_SDK_BUILDENV = $(DOCKER_SONIC_SDK_BUILDENV_STEM).gz +DOCKER_SONIC_SDK_BUILDENV_DBG = $(DOCKER_SONIC_SDK_BUILDENV_STEM)-$(DBG_IMAGE_MARK).gz + +$(DOCKER_SONIC_SDK_BUILDENV)_PATH = $(DOCKERS_PATH)/docker-sonic-sdk-buildenv + +$(DOCKER_SONIC_SDK_BUILDENV)_DEPENDS += $(LIBSAIVS) \ + $(LIBSAIVS_DEV) \ + $(LIBSAIREDIS_DEV) \ + $(LIBSAIMETADATA_DEV) \ + $(LIBSWSSCOMMON_DEV) \ + $(LIBHIREDIS_DEV) \ + $(LIBNL3_DEV) \ + $(LIBNL_GENL3_DEV) \ + $(LIBNL_ROUTE3_DEV) \ + $(LIBNL_NF3_DEV) \ + $(LIBNL_CLI_DEV) + +$(DOCKER_SONIC_SDK_BUILDENV)_DBG_DEPENDS = $($(DOCKER_SONIC_SDK)_DBG_DEPENDS) \ + $(LIBSAIVS_DBG) +$(DOCKER_SONIC_SDK_BUILDENV)_DBG_IMAGE_PACKAGES = $($(DOCKER_SONIC_SDK)_DBG_IMAGE_PACKAGES) + + +$(DOCKER_SONIC_SDK_BUILDENV)_LOAD_DOCKERS += $(DOCKER_SONIC_SDK) + +SONIC_DOCKER_IMAGES += $(DOCKER_SONIC_SDK_BUILDENV) +SONIC_DOCKER_DBG_IMAGES += $(DOCKER_SONIC_SDK_BUILDENV_DBG) diff --git a/rules/docker-sonic-sdk.dep b/rules/docker-sonic-sdk.dep new file mode 100644 index 00000000000..d9507aa16b7 --- /dev/null +++ b/rules/docker-sonic-sdk.dep @@ -0,0 +1,12 @@ + +DPATH := $($(DOCKER_SONIC_SDK)_PATH) +DEP_FILES := $(SONIC_COMMON_FILES_LIST) rules/docker-sonic-sdk.mk rules/docker-sonic-sdk.dep +DEP_FILES += $(SONIC_COMMON_BASE_FILES_LIST) +DEP_FILES += $(shell git ls-files $(DPATH)) + +$(DOCKER_SONIC_SDK)_CACHE_MODE := GIT_CONTENT_SHA +$(DOCKER_SONIC_SDK)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST) +$(DOCKER_SONIC_SDK)_DEP_FILES := $(DEP_FILES) + +$(eval $(call add_dbg_docker,$(DOCKER_SONIC_SDK),$(DOCKER_SONIC_SDK_DBG))) + diff --git a/rules/docker-sonic-sdk.mk b/rules/docker-sonic-sdk.mk new file mode 100644 index 00000000000..b6a185c6d94 --- /dev/null +++ b/rules/docker-sonic-sdk.mk @@ -0,0 +1,24 @@ +# SONiC SDK Docker Image + +DOCKER_SONIC_SDK_STEM = sonic-sdk +DOCKER_SONIC_SDK = $(DOCKER_SONIC_SDK_STEM).gz +DOCKER_SONIC_SDK_DBG = $(DOCKER_SONIC_SDK_STEM)-$(DBG_IMAGE_MARK).gz + +$(DOCKER_SONIC_SDK)_PATH = $(DOCKERS_PATH)/docker-sonic-sdk + +$(DOCKER_SONIC_SDK)_DEPENDS += $(LIBSAIREDIS) \ + $(LIBSAIMETADATA) +$(DOCKER_SONIC_SDK)_LOAD_DOCKERS += $(DOCKER_CONFIG_ENGINE_BUSTER) + +$(DOCKER_SONIC_SDK)_DBG_DEPENDS = $($(DOCKER_CONFIG_ENGINE_BUSTER)_DBG_DEPENDS) \ + $(LIBSAIREDIS_DBG) \ + $(LIBSAIMETADATA_DBG) +$(DOCKER_SONIC_SDK)_DBG_IMAGE_PACKAGES = $($(DOCKER_CONFIG_ENGINE_BUSTER)_DBG_IMAGE_PACKAGES) + +SONIC_DOCKER_IMAGES += $(DOCKER_SONIC_SDK) +SONIC_DOCKER_DBG_IMAGES += $(DOCKER_SONIC_SDK_DBG) + +$(DOCKER_SONIC_SDK)_LABELS += com.azure.sonic.versions.libswsscommon=$(LIBSWSSCOMMON_VERSION) +$(DOCKER_SONIC_SDK)_LABELS += com.azure.sonic.versions.libsairedis=$(LIBSAIREDIS_VERSION) + +$(DOCKER_SONIC_SDK)_FILES += $(SUPERVISOR_PROC_EXIT_LISTENER_SCRIPT) diff --git a/rules/functions b/rules/functions index 479f5f70c4a..705cb654e76 100644 --- a/rules/functions +++ b/rules/functions @@ -85,6 +85,8 @@ $(2)_MAIN_DEB = $(1) $(1)_DERIVED_DEBS += $(2) $(2)_URL = $($(1)_URL) $(2)_SRC_PATH = $($(1)_SRC_PATH) +$(2)_NAME = $($(1)_NAME) +$(2)_VERSION = $($(1)_VERSION) SONIC_DERIVED_DEBS += $(2) endef diff --git a/rules/sairedis.mk b/rules/sairedis.mk index 67148d1b581..1d38a74b143 100644 --- a/rules/sairedis.mk +++ b/rules/sairedis.mk @@ -1,41 +1,46 @@ # sairedis package -LIBSAIREDIS = libsairedis_1.0.0_$(CONFIGURED_ARCH).deb +LIBSAIREDIS_VERSION = 1.0.0 +LIBSAIREDIS_NAME = libsairedis + +LIBSAIREDIS = $(LIBSAIREDIS_NAME)_$(LIBSAIREDIS_VERSION)_$(CONFIGURED_ARCH).deb $(LIBSAIREDIS)_DPKG_TARGET = binary-sairedis $(LIBSAIREDIS)_SRC_PATH = $(SRC_PATH)/sonic-sairedis +$(LIBSAIREDIS)_VERSION = $(LIBSAIREDIS_VERSION) +$(LIBSAIREDIS)_NAME = $(LIBSAIREDIS_NAME) $(LIBSAIREDIS)_DEPENDS += $(LIBSWSSCOMMON_DEV) $(LIBSAIREDIS)_RDEPENDS += $(LIBSWSSCOMMON) $(LIBSAIREDIS)_DEB_BUILD_OPTIONS = nocheck SONIC_DPKG_DEBS += $(LIBSAIREDIS) -LIBSAIREDIS_DEV = libsairedis-dev_1.0.0_$(CONFIGURED_ARCH).deb +LIBSAIREDIS_DEV = $(LIBSAIREDIS_NAME)-dev_$(LIBSAIREDIS_VERSION)_$(CONFIGURED_ARCH).deb $(eval $(call add_derived_package,$(LIBSAIREDIS),$(LIBSAIREDIS_DEV))) -LIBSAIVS = libsaivs_1.0.0_$(CONFIGURED_ARCH).deb +LIBSAIVS = libsaivs_$(LIBSAIREDIS_VERSION)_$(CONFIGURED_ARCH).deb $(eval $(call add_derived_package,$(LIBSAIREDIS),$(LIBSAIVS))) -LIBSAIVS_DEV = libsaivs-dev_1.0.0_$(CONFIGURED_ARCH).deb +LIBSAIVS_DEV = libsaivs-dev_$(LIBSAIREDIS_VERSION)_$(CONFIGURED_ARCH).deb $(eval $(call add_derived_package,$(LIBSAIREDIS),$(LIBSAIVS_DEV))) $(LIBSAIVS_DEV)_DEPENDS += $(LIBSAIVS) -LIBSAIMETADATA = libsaimetadata_1.0.0_$(CONFIGURED_ARCH).deb +LIBSAIMETADATA = libsaimetadata_$(LIBSAIREDIS_VERSION)_$(CONFIGURED_ARCH).deb $(eval $(call add_derived_package,$(LIBSAIREDIS),$(LIBSAIMETADATA))) -LIBSAIMETADATA_DEV = libsaimetadata-dev_1.0.0_$(CONFIGURED_ARCH).deb +LIBSAIMETADATA_DEV = libsaimetadata-dev_$(LIBSAIREDIS_VERSION)_$(CONFIGURED_ARCH).deb $(LIBSAIMETADATA_DEV)_DEPENDS += $(LIBSAIMETADATA) $(eval $(call add_derived_package,$(LIBSAIREDIS),$(LIBSAIMETADATA_DEV))) -LIBSAIREDIS_DBG = libsairedis-dbg_1.0.0_$(CONFIGURED_ARCH).deb +LIBSAIREDIS_DBG = $(LIBSAIREDIS_NAME)-dbg_$(LIBSAIREDIS_VERSION)_$(CONFIGURED_ARCH).deb $(LIBSAIREDIS_DBG)_DEPENDS += $(LIBSAIREDIS) $(LIBSAIREDIS_DBG)_RDEPENDS += $(LIBSAIREDIS) $(eval $(call add_derived_package,$(LIBSAIREDIS),$(LIBSAIREDIS_DBG))) -LIBSAIVS_DBG = libsaivs-dbg_1.0.0_$(CONFIGURED_ARCH).deb +LIBSAIVS_DBG = libsaivs-dbg_$(LIBSAIREDIS_VERSION)_$(CONFIGURED_ARCH).deb $(LIBSAIVS_DBG)_DEPENDS += $(LIBSAIVS) $(LIBSAIVS_DBG)_RDEPENDS += $(LIBSAIVS) $(eval $(call add_derived_package,$(LIBSAIREDIS),$(LIBSAIVS_DBG))) -LIBSAIMETADATA_DBG = libsaimetadata-dbg_1.0.0_$(CONFIGURED_ARCH).deb +LIBSAIMETADATA_DBG = libsaimetadata-dbg_$(LIBSAIREDIS_VERSION)_$(CONFIGURED_ARCH).deb $(LIBSAIMETADATA_DBG)_DEPENDS += $(LIBSAIMETADATA) $(LIBSAIMETADATA_DBG)_RDEPENDS += $(LIBSAIMETADATA) $(eval $(call add_derived_package,$(LIBSAIREDIS),$(LIBSAIMETADATA_DBG))) diff --git a/rules/sonic-utilities.mk b/rules/sonic-utilities.mk index 4e5b5adbc5d..519f633cc77 100644 --- a/rules/sonic-utilities.mk +++ b/rules/sonic-utilities.mk @@ -1,8 +1,12 @@ # sonic utilities package -SONIC_UTILITIES_PY3 = sonic_utilities-1.2-py3-none-any.whl +SONIC_UTILITIES_PY3_VERSION = 1.2 +SONIC_UTILITIES_PY3_NAME = sonic_utilities +SONIC_UTILITIES_PY3 = $(SONIC_UTILITIES_PY3_NAME)-$(SONIC_UTILITIES_PY3_VERSION)-py3-none-any.whl $(SONIC_UTILITIES_PY3)_SRC_PATH = $(SRC_PATH)/sonic-utilities $(SONIC_UTILITIES_PY3)_PYTHON_VERSION = 3 +$(SONIC_UTILITIES_PY3)_NAME = $(SONIC_UTILITIES_PY3_NAME) +$(SONIC_UTILITIES_PY3)_VERSION = $(SONIC_UTILITIES_PY3_VERSION) $(SONIC_UTILITIES_PY3)_DEPENDS += $(SONIC_PY_COMMON_PY3) \ $(SWSSSDK_PY3) \ $(SONIC_CONFIG_ENGINE_PY3) \ diff --git a/rules/swss-common.mk b/rules/swss-common.mk index 8a319ea62bc..09dc312277f 100644 --- a/rules/swss-common.mk +++ b/rules/swss-common.mk @@ -1,7 +1,12 @@ # libswsscommon package -LIBSWSSCOMMON = libswsscommon_1.0.0_$(CONFIGURED_ARCH).deb +LIBSWSSCOMMON_VERSION = 1.0.0 +LIBSWSSCOMMON_NAME = libswsscommon + +LIBSWSSCOMMON = $(LIBSWSSCOMMON_NAME)_$(LIBSWSSCOMMON_VERSION)_$(CONFIGURED_ARCH).deb $(LIBSWSSCOMMON)_SRC_PATH = $(SRC_PATH)/sonic-swss-common +$(LIBSWSSCOMMON)_VERSION = $(LIBSWSSCOMMON_VERSION) +$(LIBSWSSCOMMON)_NAME = $(LIBSWSSCOMMON_NAME) $(LIBSWSSCOMMON)_DEPENDS += $(LIBHIREDIS_DEV) $(LIBNL3_DEV) $(LIBNL_GENL3_DEV) \ $(LIBNL_ROUTE3_DEV) $(LIBNL_NF3_DEV) \ $(LIBNL_CLI_DEV) @@ -9,16 +14,16 @@ $(LIBSWSSCOMMON)_RDEPENDS += $(LIBHIREDIS) $(LIBNL3) $(LIBNL_GENL3) \ $(LIBNL_ROUTE3) $(LIBNL_NF3) $(LIBNL_CLI) SONIC_DPKG_DEBS += $(LIBSWSSCOMMON) -LIBSWSSCOMMON_DEV = libswsscommon-dev_1.0.0_$(CONFIGURED_ARCH).deb +LIBSWSSCOMMON_DEV = $(LIBSWSSCOMMON_NAME)-dev_$(LIBSWSSCOMMON_VERSION)_$(CONFIGURED_ARCH).deb $(eval $(call add_derived_package,$(LIBSWSSCOMMON),$(LIBSWSSCOMMON_DEV))) -PYTHON_SWSSCOMMON = python-swsscommon_1.0.0_$(CONFIGURED_ARCH).deb +PYTHON_SWSSCOMMON = python-swsscommon_$(LIBSWSSCOMMON_VERSION)_$(CONFIGURED_ARCH).deb $(eval $(call add_derived_package,$(LIBSWSSCOMMON),$(PYTHON_SWSSCOMMON))) -PYTHON3_SWSSCOMMON = python3-swsscommon_1.0.0_$(CONFIGURED_ARCH).deb +PYTHON3_SWSSCOMMON = python3-swsscommon_$(LIBSWSSCOMMON_VERSION)_$(CONFIGURED_ARCH).deb $(eval $(call add_derived_package,$(LIBSWSSCOMMON),$(PYTHON3_SWSSCOMMON))) -LIBSWSSCOMMON_DBG = libswsscommon-dbg_1.0.0_$(CONFIGURED_ARCH).deb +LIBSWSSCOMMON_DBG = $(LIBSWSSCOMMON_NAME)-dbg_$(LIBSWSSCOMMON_VERSION)_$(CONFIGURED_ARCH).deb $(LIBSWSSCOMMON_DBG)_DEPENDS += $(LIBSWSSCOMMON) $(LIBSWSSCOMMON_DBG)_RDEPENDS += $(LIBSWSSCOMMON) $(eval $(call add_derived_package,$(LIBSWSSCOMMON),$(LIBSWSSCOMMON_DBG))) diff --git a/slave.mk b/slave.mk index 9cce5392cc2..3316b30ba29 100644 --- a/slave.mk +++ b/slave.mk @@ -747,6 +747,10 @@ $(addprefix $(TARGET_PATH)/, $(DOCKER_IMAGES)) : $(TARGET_PATH)/%.gz : .platform $(eval export $(subst -,_,$(notdir $($*.gz_PATH)))_whls=$(shell printf "$(subst $(SPACE),\n,$(call expand,$($*.gz_PYTHON_WHEELS)))\n" | awk '!a[$$0]++')) $(eval export $(subst -,_,$(notdir $($*.gz_PATH)))_dbgs=$(shell printf "$(subst $(SPACE),\n,$(call expand,$($*.gz_DBG_PACKAGES)))\n" | awk '!a[$$0]++')) $(eval export $(subst -,_,$(notdir $($*.gz_PATH)))_pkgs=$(shell printf "$(subst $(SPACE),\n,$(call expand,$($*.gz_APT_PACKAGES)))\n" | awk '!a[$$0]++')) + # Label docker image with componenets versions + $(eval export $(subst -,_,$(notdir $($*.gz_PATH)))_labels=$(foreach component,$($*.gz_DEPENDS) $($*.gz_PYTHON_DEBS) $($*.gz_PYTHON_WHEELS),\ + $(shell [[ ! -z "$($(component)_VERSION)" && ! -z "$($(component)_NAME)" ]] && \ + echo "--label com.azure.sonic.versions.$($(component)_NAME)=$($(component)_VERSION)"))) j2 $($*.gz_PATH)/Dockerfile.j2 > $($*.gz_PATH)/Dockerfile $(call generate_manifest,$*) # Prepare docker build info @@ -768,6 +772,7 @@ $(addprefix $(TARGET_PATH)/, $(DOCKER_IMAGES)) : $(TARGET_PATH)/%.gz : .platform --build-arg image_version=$(SONIC_IMAGE_VERSION) \ --label com.azure.sonic.manifest="$$(cat $($*.gz_PATH)/manifest.json)" \ --label Tag=$(SONIC_IMAGE_VERSION) \ + $($(subst -,_,$(notdir $($*.gz_PATH)))_labels) \ -t $* $($*.gz_PATH) $(LOG) scripts/collect_docker_version_files.sh $* $(TARGET_PATH) docker save $* | gzip -c > $@ @@ -835,7 +840,8 @@ SONIC_TARGET_LIST += $(addprefix $(TARGET_PATH)/, $(DOCKER_DBG_IMAGES)) DOCKER_LOAD_TARGETS = $(addsuffix -load,$(addprefix $(TARGET_PATH)/, \ $(SONIC_SIMPLE_DOCKER_IMAGES) \ - $(DOCKER_IMAGES))) + $(DOCKER_IMAGES) \ + $(DOCKER_DBG_IMAGES))) $(DOCKER_LOAD_TARGETS) : $(TARGET_PATH)/%.gz-load : .platform docker-start $$(TARGET_PATH)/$$*.gz $(HEADER) @@ -944,7 +950,9 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \ export python_swss_debs+=" $(addprefix $(IMAGE_DISTRO_DEBS_PATH)/,$(LIBSWSSCOMMON)) $(addprefix $(IMAGE_DISTRO_DEBS_PATH)/,$(PYTHON_SWSSCOMMON)) $(addprefix $(IMAGE_DISTRO_DEBS_PATH)/,$(PYTHON3_SWSSCOMMON))" export sonic_utilities_py3_wheel_path="$(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_UTILITIES_PY3))" export sonic_host_services_py3_wheel_path="$(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_HOST_SERVICES_PY3))" - + export components="$(foreach component,$(notdir $^),\ + $(shell [[ ! -z '$($(component)_VERSION)' && ! -z '$($(component)_NAME)' ]] && \ + echo $($(component)_NAME)==$($(component)_VERSION)))" $(foreach docker, $($*_DOCKERS),\ export docker_image="$(docker)" export docker_image_name="$(basename $(docker))" From 0c5c4872dc660304987da39d4ee4c0871c4a7098 Mon Sep 17 00:00:00 2001 From: jostar-yang Date: Sat, 29 May 2021 01:50:49 +0800 Subject: [PATCH 2/8] [as5835-54x] Add to support API2.0 (#6480) Add platform API 2.0 support for as5835-54x platform --- .../Accton-AS5835-54X/port_config.ini | 10 +- .../sonic_platform/__init__.py | 2 + .../sonic_platform/chassis.py | 214 +++ .../sonic_platform/component.py | 128 ++ .../sonic_platform/eeprom.py | 131 ++ .../sonic_platform/event.py | 55 + .../sonic_platform/fan.py | 179 +++ .../sonic_platform/helper.py | 117 ++ .../sonic_platform/platform.py | 21 + .../sonic_platform/psu.py | 252 ++++ .../sonic_platform/sfp.py | 1229 +++++++++++++++++ .../sonic_platform/thermal.py | 148 ++ .../as5835-54x/sonic_platform_setup.py | 34 + .../utils/accton_as5835_54x_util.py | 48 + .../debian/rules | 2 +- .../sonic-platform-accton-as5835-54x.install | 2 + 16 files changed, 2566 insertions(+), 6 deletions(-) create mode 100644 device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/__init__.py create mode 100644 device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/chassis.py create mode 100644 device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/component.py create mode 100644 device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/eeprom.py create mode 100644 device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/event.py create mode 100644 device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/fan.py create mode 100644 device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/helper.py create mode 100644 device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/platform.py create mode 100644 device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/psu.py create mode 100644 device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/sfp.py create mode 100644 device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/thermal.py create mode 100644 platform/broadcom/sonic-platform-modules-accton/as5835-54x/sonic_platform_setup.py create mode 100644 platform/broadcom/sonic-platform-modules-accton/debian/sonic-platform-accton-as5835-54x.install diff --git a/device/accton/x86_64-accton_as5835_54x-r0/Accton-AS5835-54X/port_config.ini b/device/accton/x86_64-accton_as5835_54x-r0/Accton-AS5835-54X/port_config.ini index 1e4405d1a69..99de7329aab 100644 --- a/device/accton/x86_64-accton_as5835_54x-r0/Accton-AS5835-54X/port_config.ini +++ b/device/accton/x86_64-accton_as5835_54x-r0/Accton-AS5835-54X/port_config.ini @@ -48,8 +48,8 @@ Ethernet45 74 tenGigE46 46 10000 Ethernet46 75 tenGigE47 47 10000 Ethernet47 76 tenGigE48 48 10000 Ethernet48 37,38,39,40 hundredGigE49 49 100000 -Ethernet52 29,30,31,32 hundredGigE50 53 100000 -Ethernet56 33,34,35,36 hundredGigE51 57 100000 -Ethernet60 49,50,51,52 hundredGigE52 61 100000 -Ethernet64 45,46,47,48 hundredGigE53 65 100000 -Ethernet68 41,42,43,44 hundredGigE54 69 100000 +Ethernet52 29,30,31,32 hundredGigE50 50 100000 +Ethernet56 33,34,35,36 hundredGigE51 51 100000 +Ethernet60 49,50,51,52 hundredGigE52 52 100000 +Ethernet64 45,46,47,48 hundredGigE53 53 100000 +Ethernet68 41,42,43,44 hundredGigE54 54 100000 diff --git a/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/__init__.py b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/__init__.py new file mode 100644 index 00000000000..43435472a42 --- /dev/null +++ b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/__init__.py @@ -0,0 +1,2 @@ +__all__ = ['chassis', 'eeprom', 'platform', 'psu', 'sfp', 'thermal', 'fan'] +from . import platform diff --git a/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/chassis.py b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/chassis.py new file mode 100644 index 00000000000..49805d6d785 --- /dev/null +++ b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/chassis.py @@ -0,0 +1,214 @@ +############################################################################# +# Edgecore +# +# Module contains an implementation of SONiC Platform Base API and +# provides the Chassis information which are available in the platform +# +############################################################################# + +import os +import sys + +try: + from sonic_platform_base.chassis_base import ChassisBase + from .helper import APIHelper + from .event import SfpEvent +except ImportError as e: + raise ImportError(str(e) + "- required module not found") + +NUM_FAN_TRAY = 5 +NUM_FAN = 2 +NUM_PSU = 2 +NUM_THERMAL = 4 +NUM_QSFP = 54 +PORT_START = 1 +PORT_END = 54 +NUM_COMPONENT = 4 +HOST_REBOOT_CAUSE_PATH = "/host/reboot-cause/" +PMON_REBOOT_CAUSE_PATH = "/usr/share/sonic/platform/api_files/reboot-cause/" +REBOOT_CAUSE_FILE = "reboot-cause.txt" +PREV_REBOOT_CAUSE_FILE = "previous-reboot-cause.txt" +HOST_CHK_CMD = "docker > /dev/null 2>&1" + + +class Chassis(ChassisBase): + """Platform-specific Chassis class""" + + def __init__(self): + ChassisBase.__init__(self) + self._api_helper = APIHelper() + self._api_helper = APIHelper() + self.is_host = self._api_helper.is_host() + + self.config_data = {} + + self.__initialize_fan() + self.__initialize_psu() + self.__initialize_thermals() + self.__initialize_components() + self.__initialize_sfp() + self.__initialize_eeprom() + + def __initialize_sfp(self): + from sonic_platform.sfp import Sfp + for index in range(0, PORT_END): + sfp = Sfp(index) + self._sfp_list.append(sfp) + self.sfp_module_initialized = True + + def __initialize_fan(self): + from sonic_platform.fan import Fan + for fant_index in range(0, NUM_FAN_TRAY): + for fan_index in range(0, NUM_FAN): + fan = Fan(fant_index, fan_index) + self._fan_list.append(fan) + + def __initialize_psu(self): + from sonic_platform.psu import Psu + for index in range(0, NUM_PSU): + psu = Psu(index) + self._psu_list.append(psu) + + def __initialize_thermals(self): + from sonic_platform.thermal import Thermal + for index in range(0, NUM_THERMAL): + thermal = Thermal(index) + self._thermal_list.append(thermal) + + def __initialize_eeprom(self): + from sonic_platform.eeprom import Tlv + self._eeprom = Tlv() + + def __initialize_components(self): + from sonic_platform.component import Component + for index in range(0, NUM_COMPONENT): + component = Component(index) + self._component_list.append(component) + + def __initialize_watchdog(self): + from sonic_platform.watchdog import Watchdog + self._watchdog = Watchdog() + + + def __is_host(self): + return os.system(HOST_CHK_CMD) == 0 + + def __read_txt_file(self, file_path): + try: + with open(file_path, 'r') as fd: + data = fd.read() + return data.strip() + except IOError: + pass + return None + + def get_model(self): + """ + Retrieves the model number (or part number) of the device + Returns: + string: Model/part number of device + """ + return self._eeprom.get_pn() + + def get_name(self): + """ + Retrieves the name of the device + Returns: + string: The name of the device + """ + + return self._api_helper.hwsku + + def get_presence(self): + """ + Retrieves the presence of the Chassis + Returns: + bool: True if Chassis is present, False if not + """ + return True + + def get_status(self): + """ + Retrieves the operational status of the device + Returns: + A boolean value, True if device is operating properly, False if not + """ + return True + + def get_base_mac(self): + """ + Retrieves the base MAC address for the chassis + Returns: + A string containing the MAC address in the format + 'XX:XX:XX:XX:XX:XX' + """ + return self._eeprom.get_mac() + + def get_serial_number(self): + """ + Retrieves the hardware serial number for the chassis + Returns: + A string containing the hardware serial number for this chassis. + """ + return self._eeprom.get_serial() + + def get_system_eeprom_info(self): + """ + Retrieves the full content of system EEPROM information for the chassis + Returns: + A dictionary where keys are the type code defined in + OCP ONIE TlvInfo EEPROM format and values are their corresponding + values. + """ + return self._eeprom.get_eeprom() + + def get_reboot_cause(self): + """ + Retrieves the cause of the previous reboot + + Returns: + A tuple (string, string) where the first element is a string + containing the cause of the previous reboot. This string must be + one of the predefined strings in this class. If the first string + is "REBOOT_CAUSE_HARDWARE_OTHER", the second string can be used + to pass a description of the reboot cause. + """ + + reboot_cause_path = (HOST_REBOOT_CAUSE_PATH + REBOOT_CAUSE_FILE) + sw_reboot_cause = self._api_helper.read_txt_file( + reboot_cause_path) or "Unknown" + + + return ('REBOOT_CAUSE_NON_HARDWARE', sw_reboot_cause) + + def get_change_event(self, timeout=0): + # SFP event + if not self.sfp_module_initialized: + self.__initialize_sfp() + + status, sfp_event = SfpEvent(self._sfp_list).get_sfp_event(timeout) + + return status, sfp_event + + def get_sfp(self, index): + """ + Retrieves sfp represented by (1-based) index + Args: + index: An integer, the index (1-based) of the sfp to retrieve. + The index should be the sequence of a physical port in a chassis, + starting from 1. + For example, 1 for Ethernet0, 2 for Ethernet4 and so on. + Returns: + An object dervied from SfpBase representing the specified sfp + """ + sfp = None + if not self.sfp_module_initialized: + self.__initialize_sfp() + + try: + # The index will start from 1 + sfp = self._sfp_list[index-1] + except IndexError: + sys.stderr.write("SFP index {} out of range (1-{})\n".format( + index, len(self._sfp_list))) + return sfp diff --git a/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/component.py b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/component.py new file mode 100644 index 00000000000..deebd5936d0 --- /dev/null +++ b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/component.py @@ -0,0 +1,128 @@ +############################################################################# +# Celestica +# +# Component contains an implementation of SONiC Platform Base API and +# provides the components firmware management function +# +############################################################################# + +import shlex +import subprocess + + +try: + from sonic_platform_base.component_base import ComponentBase + from .helper import APIHelper +except ImportError as e: + raise ImportError(str(e) + "- required module not found") + +CPLD_ADDR_MAPPING = { + "CPLD1": "3-0060", + "CPLD2": "3-0061", + "CPLD3": "3-0062", +} +SYSFS_PATH = "/sys/bus/i2c/devices/" +BIOS_VERSION_PATH = "/sys/class/dmi/id/bios_version" +COMPONENT_LIST= [ + ("CPLD1", "CPLD 1"), + ("CPLD2", "CPLD 2"), + ("CPLD3", "CPLD 3"), + ("BIOS", "Basic Input/Output System") + +] +COMPONENT_DES_LIST = ["CPLD","Basic Input/Output System"] + + +class Component(ComponentBase): + """Platform-specific Component class""" + + DEVICE_TYPE = "component" + + def __init__(self, component_index=0): + self._api_helper=APIHelper() + ComponentBase.__init__(self) + self.index = component_index + self.name = self.get_name() + + + def __run_command(self, command): + # Run bash command and print output to stdout + try: + process = subprocess.Popen( + shlex.split(command), stdout=subprocess.PIPE) + while True: + output = process.stdout.readline() + if output == '' and process.poll() is not None: + break + rc = process.poll() + if rc != 0: + return False + except Exception: + return False + return True + + def __get_bios_version(self): + # Retrieves the BIOS firmware version + try: + with open(BIOS_VERSION_PATH, 'r') as fd: + bios_version = fd.read() + return bios_version.strip() + except Exception as e: + print('Get exception when read bios') + return None + + def __get_cpld_version(self): + # Retrieves the CPLD firmware version + cpld_version = dict() + for cpld_name in CPLD_ADDR_MAPPING: + try: + cpld_path = "{}{}{}".format(SYSFS_PATH, CPLD_ADDR_MAPPING[cpld_name], '/version') + cpld_version_raw= self._api_helper.read_txt_file(cpld_path) + cpld_version[cpld_name] = "{}".format(int(cpld_version_raw,16)) + except Exception as e: + print('Get exception when read cpld') + cpld_version[cpld_name] = 'None' + + return cpld_version + + def get_name(self): + """ + Retrieves the name of the component + Returns: + A string containing the name of the component + """ + return COMPONENT_LIST[self.index][0] + + def get_description(self): + """ + Retrieves the description of the component + Returns: + A string containing the description of the component + """ + return COMPONENT_LIST[self.index][1] + #return "testhwsku" + + def get_firmware_version(self): + """ + Retrieves the firmware version of module + Returns: + string: The firmware versions of the module + """ + fw_version = None + if self.name == "BIOS": + fw_version = self.__get_bios_version() + elif "CPLD" in self.name: + cpld_version = self.__get_cpld_version() + fw_version = cpld_version.get(self.name) + + return fw_version + + def install_firmware(self, image_path): + """ + Install firmware to module + Args: + image_path: A string, path to firmware image + Returns: + A boolean, True if install successfully, False if not + """ + raise NotImplementedError diff --git a/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/eeprom.py b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/eeprom.py new file mode 100644 index 00000000000..f3bdcbccad3 --- /dev/null +++ b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/eeprom.py @@ -0,0 +1,131 @@ +try: + import os + import sys + import re + if sys.version_info[0] >= 3: + from io import StringIO + else: + from cStringIO import StringIO + + from sonic_platform_base.sonic_eeprom import eeprom_tlvinfo +except ImportError as e: + raise ImportError(str(e) + "- required module not found") + +CACHE_ROOT = '/var/cache/sonic/decode-syseeprom' +CACHE_FILE = 'syseeprom_cache' +NULL = 'N/A' + +class Tlv(eeprom_tlvinfo.TlvInfoDecoder): + + EEPROM_DECODE_HEADLINES = 6 + + def __init__(self): + self._eeprom_path = "/sys/bus/i2c/devices/0-0057/eeprom" + super(Tlv, self).__init__(self._eeprom_path, 0, '', True) + self._eeprom = self._load_eeprom() + + def __parse_output(self, decode_output): + decode_output.replace('\0', '') + lines = decode_output.split('\n') + lines = lines[self.EEPROM_DECODE_HEADLINES:] + _eeprom_info_dict = dict() + + for line in lines: + try: + match = re.search( + '(0x[0-9a-fA-F]{2})([\s]+[\S]+[\s]+)([\S]+)', line) + if match is not None: + idx = match.group(1) + value = match.group(3).rstrip('\0') + + _eeprom_info_dict[idx] = value + except Exception: + pass + + return _eeprom_info_dict + + def _load_eeprom(self): + original_stdout = sys.stdout + sys.stdout = StringIO() + try: + self.read_eeprom_db() + except Exception: + decode_output = sys.stdout.getvalue() + sys.stdout = original_stdout + return self.__parse_output(decode_output) + + status = self.check_status() + if 'ok' not in status: + return False + + if not os.path.exists(CACHE_ROOT): + try: + os.makedirs(CACHE_ROOT) + except Exception: + pass + + # + # only the eeprom classes that inherit from eeprom_base + # support caching. Others will work normally + # + try: + self.set_cache_name(os.path.join(CACHE_ROOT, CACHE_FILE)) + except Exception: + pass + + e = self.read_eeprom() + if e is None: + return 0 + + try: + self.update_cache(e) + except Exception: + pass + + self.decode_eeprom(e) + decode_output = sys.stdout.getvalue() + sys.stdout = original_stdout + + (is_valid, valid_crc) = self.is_checksum_valid(e) + if not is_valid: + return False + + return self.__parse_output(decode_output) + + def _valid_tlv(self, eeprom_data): + tlvinfo_type_codes_list = [ + self._TLV_CODE_PRODUCT_NAME, + self._TLV_CODE_PART_NUMBER, + self._TLV_CODE_SERIAL_NUMBER, + self._TLV_CODE_MAC_BASE, + self._TLV_CODE_MANUF_DATE, + self._TLV_CODE_DEVICE_VERSION, + self._TLV_CODE_LABEL_REVISION, + self._TLV_CODE_PLATFORM_NAME, + self._TLV_CODE_ONIE_VERSION, + self._TLV_CODE_MAC_SIZE, + self._TLV_CODE_MANUF_NAME, + self._TLV_CODE_MANUF_COUNTRY, + self._TLV_CODE_VENDOR_NAME, + self._TLV_CODE_DIAG_VERSION, + self._TLV_CODE_SERVICE_TAG, + self._TLV_CODE_VENDOR_EXT, + self._TLV_CODE_CRC_32 + ] + + for code in tlvinfo_type_codes_list: + code_str = "0x{:X}".format(code) + eeprom_data[code_str] = eeprom_data.get(code_str, NULL) + return eeprom_data + + def get_eeprom(self): + return self._valid_tlv(self._eeprom) + + def get_pn(self): + return self._eeprom.get('0x22', NULL) + + def get_serial(self): + return self._eeprom.get('0x23', NULL) + + def get_mac(self): + return self._eeprom.get('0x24', NULL) diff --git a/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/event.py b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/event.py new file mode 100644 index 00000000000..d77ee8c29dc --- /dev/null +++ b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/event.py @@ -0,0 +1,55 @@ +try: + import time + from .helper import APIHelper + from sonic_py_common.logger import Logger +except ImportError as e: + raise ImportError(repr(e) + " - required module not found") + + +class SfpEvent: + ''' Listen to insert/remove sfp events ''' + + def __init__(self, sfp_list): + self._api_helper = APIHelper() + self._sfp_list = sfp_list + self._logger = Logger() + + sfp_change_event_data = {'valid': 0, 'last': 0, 'present': 0} + def get_sfp_event(self, timeout=2000): + now = time.time() + port_dict = {} + change_dict = {} + change_dict['sfp'] = port_dict + + if timeout < 1000: + timeout = 1000 + timeout = timeout / float(1000) # Convert to secs + + if now < (self.sfp_change_event_data['last'] + timeout) and self.sfp_change_event_data['valid']: + return True, change_dict + + bitmap = 0 + for sfp in self._sfp_list: + modpres = sfp.get_presence() + i=sfp.port_num-1 + if modpres: + bitmap = bitmap | (1 << i) + + changed_ports = self.sfp_change_event_data['present'] ^ bitmap + if changed_ports: + for sfp in self._sfp_list: + i=sfp.port_num-1 + if (changed_ports & (1 << i)): + if (bitmap & (1 << i)) == 0: + port_dict[i+1] = '0' + else: + port_dict[i+1] = '1' + + + # Update the cache dict + self.sfp_change_event_data['present'] = bitmap + self.sfp_change_event_data['last'] = now + self.sfp_change_event_data['valid'] = 1 + return True, change_dict + else: + return True, change_dict diff --git a/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/fan.py b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/fan.py new file mode 100644 index 00000000000..e38f96d6f05 --- /dev/null +++ b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/fan.py @@ -0,0 +1,179 @@ +############################################################################# +# Edgecore +# +# Module contains an implementation of SONiC Platform Base API and +# provides the fan status which are available in the platform +# +############################################################################# + + + +try: + from sonic_platform_base.fan_base import FanBase + from .helper import APIHelper +except ImportError as e: + raise ImportError(str(e) + "- required module not found") + +PSU_FAN_MAX_RPM = 26688 +CPLD_I2C_PATH = "/sys/bus/i2c/devices/3-0063/fan" +PSU_HWMON_I2C_PATH ="/sys/bus/i2c/devices/{}-00{}/" +PSU_I2C_MAPPING = { + 0: { + "num": 11, + "addr": "58" + }, + 1: { + "num": 12, + "addr": "5b" + }, +} + + +class Fan(FanBase): + """Platform-specific Fan class""" + + def __init__(self, fan_tray_index, fan_index=0, is_psu_fan=False, psu_index=0): + self._api_helper=APIHelper() + self.fan_index = fan_index + self.fan_tray_index = fan_tray_index + self.is_psu_fan = is_psu_fan + if self.is_psu_fan: + self.psu_index = psu_index + self.psu_i2c_num = PSU_I2C_MAPPING[self.psu_index]['num'] + self.psu_i2c_addr = PSU_I2C_MAPPING[self.psu_index]['addr'] + self.psu_hwmon_path = PSU_HWMON_I2C_PATH.format( + self.psu_i2c_num, self.psu_i2c_addr) + + FanBase.__init__(self) + + + def get_direction(self): + """ + Retrieves the direction of fan + Returns: + A string, either FAN_DIRECTION_INTAKE or FAN_DIRECTION_EXHAUST + depending on fan direction + """ + + + if not self.is_psu_fan: + dir_str = "{}{}{}".format(CPLD_I2C_PATH, self.fan_tray_index, '_direction') + val=self._api_helper.read_txt_file(dir_str) + if val is not None: + if val==0: + direction=self.FAN_DIRECTION_EXHAUST + else: + direction=self.FAN_DIRECTION_INTAKE + else: + direction=self.FAN_DIRECTION_EXHAUST + else: #For PSU + dir_str = "{}{}".format(self.psu_hwmon_path,'psu_fan_dir') + + val=self._api_helper.read_txt_file(dir_str) + + if val is not None: + if val=='F2B': + direction=self.FAN_DIRECTION_EXHAUST + else: + direction=self.FAN_DIRECTION_INTAKE + else: + direction=self.FAN_DIRECTION_EXHAUST + + return direction + + def get_speed(self): + """ + Retrieves the speed of fan as a percentage of full speed + Returns: + An integer, the percentage of full fan speed, in the range 0 (off) + to 100 (full speed) + + """ + speed = 0 + if self.is_psu_fan: + psu_fan_path= "{}{}".format(self.psu_hwmon_path, 'psu_fan1_speed_rpm') + fan_speed_rpm = self._api_helper.read_txt_file(psu_fan_path) + if fan_speed_rpm is not None: + speed = (int(fan_speed_rpm,10))*100/26688 + if speed > 100: + speed=100 + else: + return 0 + elif self.get_presence(): + speed_path = "{}{}".format(CPLD_I2C_PATH, '_duty_cycle_percentage') + speed=self._api_helper.read_txt_file(speed_path) + if speed is None: + return 0 + + return int(speed) + + def get_target_speed(self): + """ + Retrieves the target (expected) speed of the fan + Returns: + An integer, the percentage of full fan speed, in the range 0 (off) + to 100 (full speed) + + Note: + speed_pc = pwm_target/255*100 + + 0 : when PWM mode is use + pwm : when pwm mode is not use + """ + return False #Not supported + + def get_speed_tolerance(self): + """ + Retrieves the speed tolerance of the fan + Returns: + An integer, the percentage of variance from target speed which is + considered tolerable + """ + return False #Not supported + + def set_speed(self, speed): + """ + Sets the fan speed + Args: + speed: An integer, the percentage of full fan speed to set fan to, + in the range 0 (off) to 100 (full speed) + Returns: + A boolean, True if speed is set successfully, False if not + + """ + + if not self.is_psu_fan and self.get_presence(): + speed_path = "{}{}".format(CPLD_I2C_PATH, '_duty_cycle_percentage') + return self._api_helper.write_txt_file(speed_path, int(speed)) + + return False + + def set_status_led(self, color): + """ + Sets the state of the fan module status LED + Args: + color: A string representing the color with which to set the + fan module status LED + Returns: + bool: True if status LED state is set successfully, False if not + """ + return False #Not supported + + def get_presence(self): + """ + Retrieves the presence of the FAN + Returns: + bool: True if FAN is present, False if not + """ + present_path = "{}{}{}".format(CPLD_I2C_PATH, self.fan_tray_index+1, '_present') + val=self._api_helper.read_txt_file(present_path) + + if not self.is_psu_fan: + if val is not None: + return int(val, 10)==1 + else: + return False + + else: + return True + diff --git a/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/helper.py b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/helper.py new file mode 100644 index 00000000000..4cd60ac9061 --- /dev/null +++ b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/helper.py @@ -0,0 +1,117 @@ +import os +import struct +import subprocess +from mmap import * +from sonic_py_common import device_info + +HOST_CHK_CMD = "docker > /dev/null 2>&1" +EMPTY_STRING = "" + + +class APIHelper(): + + def __init__(self): + (self.platform, self.hwsku) = device_info.get_platform_and_hwsku() + + def is_host(self): + return os.system(HOST_CHK_CMD) == 0 + + def pci_get_value(self, resource, offset): + status = True + result = "" + try: + fd = os.open(resource, os.O_RDWR) + mm = mmap(fd, 0) + mm.seek(int(offset)) + read_data_stream = mm.read(4) + result = struct.unpack('I', read_data_stream) + except Exception: + status = False + return status, result + + def run_command(self, cmd): + status = True + result = "" + try: + p = subprocess.Popen( + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + raw_data, err = p.communicate() + if err == '': + result = raw_data.strip() + except Exception: + status = False + return status, result + + def run_interactive_command(self, cmd): + try: + os.system(cmd) + except Exception: + return False + return True + + def read_txt_file(self, file_path): + try: + with open(file_path, 'r') as fd: + data = fd.read() + return data.strip() + except IOError: + pass + return None + + def write_txt_file(self, file_path, value): + try: + with open(file_path, 'w') as fd: + fd.write(str(value)) + except IOError: + return False + return True + + def ipmi_raw(self, netfn, cmd): + status = True + result = "" + try: + cmd = "ipmitool raw {} {}".format(str(netfn), str(cmd)) + p = subprocess.Popen( + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + raw_data, err = p.communicate() + if err == '': + result = raw_data.strip() + else: + status = False + except Exception: + status = False + return status, result + + def ipmi_fru_id(self, id, key=None): + status = True + result = "" + try: + cmd = "ipmitool fru print {}".format(str( + id)) if not key else "ipmitool fru print {0} | grep '{1}' ".format(str(id), str(key)) + + p = subprocess.Popen( + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + raw_data, err = p.communicate() + if err == '': + result = raw_data.strip() + else: + status = False + except Exception: + status = False + return status, result + + def ipmi_set_ss_thres(self, id, threshold_key, value): + status = True + result = "" + try: + cmd = "ipmitool sensor thresh '{}' {} {}".format(str(id), str(threshold_key), str(value)) + p = subprocess.Popen( + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + raw_data, err = p.communicate() + if err == '': + result = raw_data.strip() + else: + status = False + except Exception: + status = False + return status, result diff --git a/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/platform.py b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/platform.py new file mode 100644 index 00000000000..2f2c2a447fc --- /dev/null +++ b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/platform.py @@ -0,0 +1,21 @@ +############################################################################# +# Edgecore +# +# Module contains an implementation of SONiC Platform Base API and +# provides the platform information +# +############################################################################# + +try: + from sonic_platform_base.platform_base import PlatformBase + from sonic_platform.chassis import Chassis +except ImportError as e: + raise ImportError(str(e) + "- required module not found") + + +class Platform(PlatformBase): + """Platform-specific Platform class""" + + def __init__(self): + PlatformBase.__init__(self) + self._chassis = Chassis() diff --git a/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/psu.py b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/psu.py new file mode 100644 index 00000000000..78c91df8a84 --- /dev/null +++ b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/psu.py @@ -0,0 +1,252 @@ +############################################################################# +# Edgecore +# +# Module contains an implementation of SONiC Platform Base API and +# provides the PSUs status which are available in the platform +# +############################################################################# + +#import sonic_platform + +try: + from sonic_platform_base.psu_base import PsuBase + #from sonic_platform.fan import Fan + from .helper import APIHelper +except ImportError as e: + raise ImportError(str(e) + "- required module not found") + + +I2C_PATH ="/sys/bus/i2c/devices/{0}-00{1}/" + +PSU_NAME_LIST = ["PSU-1", "PSU-2"] +PSU_NUM_FAN = [1, 1] +PSU_HWMON_I2C_MAPPING = { + 0: { + "num": 11, + "addr": "58" + }, + 1: { + "num": 12, + "addr": "5b" + }, +} + +PSU_CPLD_I2C_MAPPING = { + 0: { + "num": 11, + "addr": "50" + }, + 1: { + "num": 12, + "addr": "53" + }, +} + +class Psu(PsuBase): + """Platform-specific Psu class""" + + def __init__(self, psu_index=0): + PsuBase.__init__(self) + self.index = psu_index + self._api_helper = APIHelper() + + self.i2c_num = PSU_HWMON_I2C_MAPPING[self.index]["num"] + self.i2c_addr = PSU_HWMON_I2C_MAPPING[self.index]["addr"] + self.hwmon_path = I2C_PATH.format(self.i2c_num, self.i2c_addr) + + self.i2c_num = PSU_CPLD_I2C_MAPPING[self.index]["num"] + self.i2c_addr = PSU_CPLD_I2C_MAPPING[self.index]["addr"] + self.cpld_path = I2C_PATH.format(self.i2c_num, self.i2c_addr) + self.__initialize_fan() + + def __initialize_fan(self): + from sonic_platform.fan import Fan + for fan_index in range(0, PSU_NUM_FAN[self.index]): + fan = Fan(fan_index, 0, is_psu_fan=True, psu_index=self.index) + self._fan_list.append(fan) + + def get_voltage(self): + """ + Retrieves current PSU voltage output + Returns: + A float number, the output voltage in volts, + e.g. 12.1 + """ + vout_path = "{}{}".format(self.hwmon_path, 'psu_v_out') + vout_val=self._api_helper.read_txt_file(vout_path) + if vout_val is not None: + return float(vout_val)/ 1000 + else: + return 0 + + def get_current(self): + """ + Retrieves present electric current supplied by PSU + Returns: + A float number, the electric current in amperes, e.g 15.4 + """ + iout_path = "{}{}".format(self.hwmon_path, 'psu_i_out') + val=self._api_helper.read_txt_file(iout_path) + if val is not None: + return float(val)/1000 + else: + return 0 + + def get_power(self): + """ + Retrieves current energy supplied by PSU + Returns: + A float number, the power in watts, e.g. 302.6 + """ + pout_path = "{}{}".format(self.hwmon_path, 'psu_p_out') + val=self._api_helper.read_txt_file(pout_path) + if val is not None: + return float(val)/1000 + else: + return 0 + + def get_powergood_status(self): + """ + Retrieves the powergood status of PSU + Returns: + A boolean, True if PSU has stablized its output voltages and passed all + its internal self-tests, False if not. + """ + return self.get_status() + + def set_status_led(self, color): + """ + Sets the state of the PSU status LED + Args: + color: A string representing the color with which to set the PSU status LED + Note: Only support green and off + Returns: + bool: True if status LED state is set successfully, False if not + """ + + return False #Controlled by HW + + def get_status_led(self): + """ + Gets the state of the PSU status LED + Returns: + A string, one of the predefined STATUS_LED_COLOR_* strings above + """ + status=self.get_status() + if not status: + return self.STATUS_LED_COLOR_RED + + if status==1: + return self.STATUS_LED_COLOR_GREEN + else: + return self.STATUS_LED_COLOR_RED + + + def get_temperature(self): + """ + Retrieves current temperature reading from PSU + Returns: + A float number of current temperature in Celsius up to nearest thousandth + of one degree Celsius, e.g. 30.125 + """ + temp_path = "{}{}".format(self.hwmon_path, 'psu_temp1_input') + val=self._api_helper.read_txt_file(temp_path) + if val is not None: + return float(val)/1000 + else: + return 0 + + def get_temperature_high_threshold(self): + """ + Retrieves the high threshold temperature of PSU + Returns: + A float number, the high threshold temperature of PSU in Celsius + up to nearest thousandth of one degree Celsius, e.g. 30.125 + """ + return False #Not supported + + def get_voltage_high_threshold(self): + """ + Retrieves the high threshold PSU voltage output + Returns: + A float number, the high threshold output voltage in volts, + e.g. 12.1 + """ + vout_path = "{}{}".format(self.hwmon_path, 'psu_mfr_vout_max') + vout_val=self._api_helper.read_txt_file(vout_path) + if vout_val is not None: + return float(vout_val)/ 1000 + else: + return 0 + + def get_voltage_low_threshold(self): + """ + Retrieves the low threshold PSU voltage output + Returns: + A float number, the low threshold output voltage in volts, + e.g. 12.1 + """ + vout_path = "{}{}".format(self.hwmon_path, 'psu_mfr_vout_min') + vout_val=self._api_helper.read_txt_file(vout_path) + if vout_val is not None: + return float(vout_val)/ 1000 + else: + return 0 + + def get_name(self): + """ + Retrieves the name of the device + Returns: + string: The name of the device + """ + return PSU_NAME_LIST[self.index] + + def get_presence(self): + """ + Retrieves the presence of the PSU + Returns: + bool: True if PSU is present, False if not + """ + presence_path="{}{}".format(self.cpld_path, 'psu_present') + val=self._api_helper.read_txt_file(presence_path) + if val is not None: + return int(val, 10) == 1 + else: + return 0 + + def get_status(self): + """ + Retrieves the operational status of the device + Returns: + A boolean value, True if device is operating properly, False if not + """ + power_path="{}{}".format(self.cpld_path, 'psu_power_good') + val=self._api_helper.read_txt_file(power_path) + if val is not None: + return int(val, 10) == 1 + else: + return 0 + + def get_model(self): + """ + Retrieves the model number (or part number) of the device + Returns: + string: Model/part number of device + """ + model_path="{}{}".format(self.cpld_path, 'psu_model_name') + model=self._api_helper.read_txt_file(model_path) + if not model: + return "N/A" + return model + + def get_serial(self): + """ + Retrieves the serial number of the device + Returns: + string: Serial number of device + """ + serial_path="{}{}".format(self.cpld_path, 'psu_serial_numer') + serial=self._api_helper.read_txt_file(serial_path) + if not serial: + return "N/A" + return serial diff --git a/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/sfp.py b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/sfp.py new file mode 100644 index 00000000000..24f91c3b7b6 --- /dev/null +++ b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/sfp.py @@ -0,0 +1,1229 @@ +############################################################################# +# Edgecore +# +# Sfp contains an implementation of SONiC Platform Base API and +# provides the sfp device status which are available in the platform +# +############################################################################# + +import os +import time +import sys + +from ctypes import create_string_buffer + +try: + from sonic_platform_base.sfp_base import SfpBase + from sonic_platform_base.sonic_sfp.sff8436 import sff8436Dom + from sonic_platform_base.sonic_sfp.sff8436 import sff8436InterfaceId + from sonic_platform_base.sonic_sfp.sff8472 import sff8472Dom + from sonic_platform_base.sonic_sfp.sff8472 import sff8472InterfaceId + from sonic_platform_base.sonic_sfp.sfputilhelper import SfpUtilHelper + from .helper import APIHelper +except ImportError as e: + raise ImportError(str(e) + "- required module not found") + +CPLD_I2C_PATH = "/sys/bus/i2c/devices/" + +QSFP_INFO_OFFSET = 128 +QSFP_DOM_OFFSET = 0 + +SFP_INFO_OFFSET = 0 +SFP_DOM_OFFSET = 256 + +XCVR_INTFACE_BULK_OFFSET = 0 +XCVR_INTFACE_BULK_WIDTH_QSFP = 20 +XCVR_INTFACE_BULK_WIDTH_SFP = 21 +XCVR_HW_REV_WIDTH_QSFP = 2 +XCVR_HW_REV_WIDTH_SFP = 4 +XCVR_CABLE_LENGTH_WIDTH_QSFP = 5 +XCVR_VENDOR_NAME_OFFSET = 20 +XCVR_VENDOR_NAME_WIDTH = 16 +XCVR_VENDOR_OUI_OFFSET = 37 +XCVR_VENDOR_OUI_WIDTH = 3 +XCVR_VENDOR_PN_OFFSET = 40 +XCVR_VENDOR_PN_WIDTH = 16 +XCVR_HW_REV_OFFSET = 56 +XCVR_HW_REV_WIDTH_OSFP = 2 +XCVR_HW_REV_WIDTH_SFP = 4 +XCVR_VENDOR_SN_OFFSET = 68 +XCVR_VENDOR_SN_WIDTH = 16 +XCVR_VENDOR_DATE_OFFSET = 84 +XCVR_VENDOR_DATE_WIDTH = 8 +XCVR_DOM_CAPABILITY_OFFSET = 92 +XCVR_DOM_CAPABILITY_WIDTH = 1 + +# Offset for values in QSFP eeprom +QSFP_DOM_REV_OFFSET = 1 +QSFP_DOM_REV_WIDTH = 1 +QSFP_TEMPE_OFFSET = 22 +QSFP_TEMPE_WIDTH = 2 +QSFP_VOLT_OFFSET = 26 +QSFP_VOLT_WIDTH = 2 +QSFP_CHANNL_MON_OFFSET = 34 +QSFP_CHANNL_MON_WIDTH = 16 +QSFP_CHANNL_MON_WITH_TX_POWER_WIDTH = 24 +QSFP_CONTROL_OFFSET = 86 +QSFP_CONTROL_WIDTH = 8 +QSFP_CHANNL_RX_LOS_STATUS_OFFSET = 3 +QSFP_CHANNL_RX_LOS_STATUS_WIDTH = 1 +QSFP_CHANNL_TX_FAULT_STATUS_OFFSET = 4 +QSFP_CHANNL_TX_FAULT_STATUS_WIDTH = 1 +QSFP_POWEROVERRIDE_OFFSET = 93 +QSFP_POWEROVERRIDE_WIDTH = 1 +QSFP_MODULE_THRESHOLD_OFFSET = 128 +QSFP_MODULE_THRESHOLD_WIDTH = 24 +QSFP_CHANNEL_THRESHOLD_OFFSET = 176 +QSFP_CHANNEL_THRESHOLD_WIDTH = 16 + +qsfp_cable_length_tup = ('Length(km)', 'Length OM3(2m)', + 'Length OM2(m)', 'Length OM1(m)', + 'Length Cable Assembly(m)') + +qsfp_compliance_code_tup = ('10/40G Ethernet Compliance Code', 'SONET Compliance codes', + 'SAS/SATA compliance codes', 'Gigabit Ethernet Compliant codes', + 'Fibre Channel link length/Transmitter Technology', + 'Fibre Channel transmission media', 'Fibre Channel Speed') + + +# Offset for values in SFP eeprom +SFP_TEMPE_OFFSET = 96 +SFP_TEMPE_WIDTH = 2 +SFP_VOLT_OFFSET = 98 +SFP_VOLT_WIDTH = 2 +SFP_CHANNL_MON_OFFSET = 100 +SFP_CHANNL_MON_WIDTH = 6 +SFP_MODULE_THRESHOLD_OFFSET = 0 +SFP_MODULE_THRESHOLD_WIDTH = 40 +SFP_CHANNL_THRESHOLD_OFFSET = 112 +SFP_CHANNL_THRESHOLD_WIDTH = 2 +SFP_STATUS_CONTROL_OFFSET = 110 +SFP_STATUS_CONTROL_WIDTH = 1 +SFP_TX_DISABLE_HARD_BIT = 7 +SFP_TX_DISABLE_SOFT_BIT = 6 + +sfp_cable_length_tup = ('LengthSMFkm-UnitsOfKm', 'LengthSMF(UnitsOf100m)', + 'Length50um(UnitsOf10m)', 'Length62.5um(UnitsOfm)', + 'LengthCable(UnitsOfm)', 'LengthOM3(UnitsOf10m)') + +sfp_compliance_code_tup = ('10GEthernetComplianceCode', 'InfinibandComplianceCode', + 'ESCONComplianceCodes', 'SONETComplianceCodes', + 'EthernetComplianceCodes', 'FibreChannelLinkLength', + 'FibreChannelTechnology', 'SFP+CableTechnology', + 'FibreChannelTransmissionMedia', 'FibreChannelSpeed') + + +class Sfp(SfpBase): + """Platform-specific Sfp class""" + + # Port number + PORT_START = 1 + PORT_END = 54 + QSFP_PORT_START = 49 + + # Path to sysfs + PLATFORM_ROOT_PATH = "/usr/share/sonic/device" + PMON_HWSKU_PATH = "/usr/share/sonic/hwsku" + HOST_CHK_CMD = "docker > /dev/null 2>&1" + + PLATFORM = "x86_64-accton_as5835_54x-r0" + HWSKU = "Accton-AS5835-54X" + + _cpld_mapping = { + 0: "3-0060", + 1: "3-0061", + 2: "3-0062", + } + _port_to_i2c_mapping = { + 1: 42, + 2: 43, + 3: 44, + 4: 45, + 5: 46, + 6: 47, + 7: 48, + 8: 49, + 9: 50, + 10: 51, + 11: 52, + 12: 53, + 13: 54, + 14: 55, + 15: 56, + 16: 57, + 17: 58, + 18: 59, + 19: 60, + 20: 61, + 21: 62, + 22: 63, + 23: 64, + 24: 65, + 25: 66, + 26: 67, + 27: 68, + 28: 69, + 29: 70, + 30: 71, + 31: 72, + 32: 73, + 33: 74, + 34: 75, + 35: 76, + 36: 77, + 37: 78, + 38: 79, + 39: 80, + 40: 81, + 41: 82, + 42: 83, + 43: 84, + 44: 85, + 45: 86, + 46: 87, + 47: 88, + 48: 89, + 49: 28, # QSFP49 + 50: 29, # QSFP50 + 51: 26, # QSFP51 + 52: 30, # QSFP52 + 53: 31, # QSFP53 + 54: 27, # QSFP54 + + } + + def __init__(self, sfp_index=0): + self._api_helper=APIHelper() + # Init index + self.index = sfp_index + self.port_num = self.index + 1 + + # Init eeprom path + eeprom_path = '/sys/bus/i2c/devices/{0}-0050/eeprom' + self.port_to_eeprom_mapping = {} + for x in range(self.PORT_START, self.PORT_END + 1): + self.port_to_eeprom_mapping[x] = eeprom_path.format(self._port_to_i2c_mapping[x]) + + self.info_dict_keys = ['type', 'hardware_rev', 'serial', 'manufacturer', 'model', 'connector', 'encoding', 'ext_identifier', + 'ext_rateselect_compliance', 'cable_type', 'cable_length', 'nominal_bit_rate', 'specification_compliance', 'vendor_date', 'vendor_oui', + 'application_advertisement', 'type_abbrv_name'] + + self.dom_dict_keys = ['rx_los', 'tx_fault', 'reset_status', 'power_lpmode', 'tx_disable', 'tx_disable_channel', 'temperature', 'voltage', + 'rx1power', 'rx2power', 'rx3power', 'rx4power', 'tx1bias', 'tx2bias', 'tx3bias', 'tx4bias', 'tx1power', 'tx2power', 'tx3power', 'tx4power'] + + self.threshold_dict_keys = ['temphighalarm', 'temphighwarning', 'templowalarm', 'templowwarning', 'vcchighalarm', 'vcchighwarning', 'vcclowalarm', 'vcclowwarning', 'rxpowerhighalarm', 'rxpowerhighwarning', + 'rxpowerlowalarm', 'rxpowerlowwarning', 'txpowerhighalarm', 'txpowerhighwarning', 'txpowerlowalarm', 'txpowerlowwarning', 'txbiashighalarm', 'txbiashighwarning', 'txbiaslowalarm', 'txbiaslowwarning'] + + SfpBase.__init__(self) + + # For cage 1~38 are at cpld2, others are at cpld3. + def __get_cpld_num(self, port_num): + return 1 if (port_num < 39) else 2 + + + def _convert_string_to_num(self, value_str): + if "-inf" in value_str: + return 'N/A' + elif "Unknown" in value_str: + return 'N/A' + elif 'dBm' in value_str: + t_str = value_str.rstrip('dBm') + return float(t_str) + elif 'mA' in value_str: + t_str = value_str.rstrip('mA') + return float(t_str) + elif 'C' in value_str: + t_str = value_str.rstrip('C') + return float(t_str) + elif 'Volts' in value_str: + t_str = value_str.rstrip('Volts') + return float(t_str) + else: + return 'N/A' + + def __write_txt_file(self, file_path, value): + try: + with open(file_path, 'w') as fd: + fd.write(str(value)) + except Exception: + return False + return True + + def __is_host(self): + return os.system(self.HOST_CHK_CMD) == 0 + + def __get_path_to_port_config_file(self): + platform_path = "/".join([self.PLATFORM_ROOT_PATH, self.PLATFORM]) + hwsku_path = "/".join([platform_path, self.HWSKU] + ) if self.__is_host() else self.PMON_HWSKU_PATH + return "/".join([hwsku_path, "port_config.ini"]) + + def __read_eeprom_specific_bytes(self, offset, num_bytes): + sysfsfile_eeprom = None + eeprom_raw = [] + for i in range(0, num_bytes): + eeprom_raw.append("0x00") + + sysfs_sfp_i2c_client_eeprom_path = self.port_to_eeprom_mapping[self.port_num] + try: + sysfsfile_eeprom = open( + sysfs_sfp_i2c_client_eeprom_path, mode="rb", buffering=0) + sysfsfile_eeprom.seek(offset) + raw = sysfsfile_eeprom.read(num_bytes) + if sys.version_info[0] >= 3: + for n in range(0, num_bytes): + eeprom_raw[n] = hex(raw[n])[2:].zfill(2) + else: + for n in range(0, num_bytes): + eeprom_raw[n] = hex(ord(raw[n]))[2:].zfill(2) + except Exception: + pass + finally: + if sysfsfile_eeprom: + sysfsfile_eeprom.close() + + return eeprom_raw + + def get_transceiver_info(self): + """ + Retrieves transceiver info of this SFP + Returns: + A dict which contains following keys/values : + ======================================================================== + keys |Value Format |Information + ---------------------------|---------------|---------------------------- + type |1*255VCHAR |type of SFP + hardware_rev |1*255VCHAR |hardware version of SFP + serial |1*255VCHAR |serial number of the SFP + manufacturer |1*255VCHAR |SFP vendor name + model |1*255VCHAR |SFP model name + connector |1*255VCHAR |connector information + encoding |1*255VCHAR |encoding information + ext_identifier |1*255VCHAR |extend identifier + ext_rateselect_compliance |1*255VCHAR |extended rateSelect compliance + cable_length |INT |cable length in m + nominal_bit_rate |INT |nominal bit rate by 100Mbs + specification_compliance |1*255VCHAR |specification compliance + vendor_date |1*255VCHAR |vendor date + vendor_oui |1*255VCHAR |vendor OUI + application_advertisement |1*255VCHAR |supported applications advertisement + ======================================================================== + """ + # check present status + if self.port_num < 49: + sfpi_obj = sff8472InterfaceId() #SFP + else: + sfpi_obj = sff8436InterfaceId() #QSFP + if not self.get_presence() or not sfpi_obj: + return {} + + if self.port_num < 49: + offset = SFP_INFO_OFFSET + sfp_interface_bulk_raw = self.__read_eeprom_specific_bytes( + (offset + XCVR_INTFACE_BULK_OFFSET), XCVR_INTFACE_BULK_WIDTH_SFP) + else: + offset = QSFP_INFO_OFFSET + sfp_interface_bulk_raw = self.__read_eeprom_specific_bytes( + (offset + XCVR_INTFACE_BULK_OFFSET), XCVR_INTFACE_BULK_WIDTH_QSFP) + + sfp_interface_bulk_data = sfpi_obj.parse_sfp_info_bulk( + sfp_interface_bulk_raw, 0) + + sfp_vendor_name_raw = self.__read_eeprom_specific_bytes( + (offset + XCVR_VENDOR_NAME_OFFSET), XCVR_VENDOR_NAME_WIDTH) + sfp_vendor_name_data = sfpi_obj.parse_vendor_name( + sfp_vendor_name_raw, 0) + + sfp_vendor_pn_raw = self.__read_eeprom_specific_bytes( + (offset + XCVR_VENDOR_PN_OFFSET), XCVR_VENDOR_PN_WIDTH) + sfp_vendor_pn_data = sfpi_obj.parse_vendor_pn( + sfp_vendor_pn_raw, 0) + + if self.port_num < 49: + sfp_vendor_rev_raw = self.__read_eeprom_specific_bytes( + (offset + XCVR_HW_REV_OFFSET), XCVR_HW_REV_WIDTH_SFP) + else: + sfp_vendor_rev_raw = self.__read_eeprom_specific_bytes( + (offset + XCVR_HW_REV_OFFSET), XCVR_HW_REV_WIDTH_QSFP) + + sfp_vendor_rev_data = sfpi_obj.parse_vendor_rev( + sfp_vendor_rev_raw, 0) + + sfp_vendor_sn_raw = self.__read_eeprom_specific_bytes( + (offset + XCVR_VENDOR_SN_OFFSET), XCVR_VENDOR_SN_WIDTH) + sfp_vendor_sn_data = sfpi_obj.parse_vendor_sn( + sfp_vendor_sn_raw, 0) + + sfp_vendor_oui_raw = self.__read_eeprom_specific_bytes( + (offset + XCVR_VENDOR_OUI_OFFSET), XCVR_VENDOR_OUI_WIDTH) + if sfp_vendor_oui_raw is not None: + sfp_vendor_oui_data = sfpi_obj.parse_vendor_oui( + sfp_vendor_oui_raw, 0) + + sfp_vendor_date_raw = self.__read_eeprom_specific_bytes( + (offset + XCVR_VENDOR_DATE_OFFSET), XCVR_VENDOR_DATE_WIDTH) + sfp_vendor_date_data = sfpi_obj.parse_vendor_date( + sfp_vendor_date_raw, 0) + + transceiver_info_dict = dict.fromkeys(self.info_dict_keys, 'N/A') + compliance_code_dict = dict() + + if sfp_interface_bulk_data: + transceiver_info_dict['type'] = sfp_interface_bulk_data['data']['type']['value'] + transceiver_info_dict['connector'] = sfp_interface_bulk_data['data']['Connector']['value'] + transceiver_info_dict['encoding'] = sfp_interface_bulk_data['data']['EncodingCodes']['value'] + transceiver_info_dict['ext_identifier'] = sfp_interface_bulk_data['data']['Extended Identifier']['value'] + transceiver_info_dict['ext_rateselect_compliance'] = sfp_interface_bulk_data['data']['RateIdentifier']['value'] + transceiver_info_dict['type_abbrv_name'] = sfp_interface_bulk_data['data']['type_abbrv_name']['value'] + + transceiver_info_dict['manufacturer'] = sfp_vendor_name_data[ + 'data']['Vendor Name']['value'] if sfp_vendor_name_data else 'N/A' + transceiver_info_dict['model'] = sfp_vendor_pn_data['data']['Vendor PN']['value'] if sfp_vendor_pn_data else 'N/A' + transceiver_info_dict['hardware_rev'] = sfp_vendor_rev_data['data']['Vendor Rev']['value'] if sfp_vendor_rev_data else 'N/A' + transceiver_info_dict['serial'] = sfp_vendor_sn_data['data']['Vendor SN']['value'] if sfp_vendor_sn_data else 'N/A' + transceiver_info_dict['vendor_oui'] = sfp_vendor_oui_data['data']['Vendor OUI']['value'] if sfp_vendor_oui_data else 'N/A' + transceiver_info_dict['vendor_date'] = sfp_vendor_date_data[ + 'data']['VendorDataCode(YYYY-MM-DD Lot)']['value'] if sfp_vendor_date_data else 'N/A' + transceiver_info_dict['cable_type'] = "Unknown" + transceiver_info_dict['cable_length'] = "Unknown" + + if self.port_num < 49: + for key in sfp_cable_length_tup: + if key in sfp_interface_bulk_data['data']: + transceiver_info_dict['cable_type'] = key + transceiver_info_dict['cable_length'] = str( + sfp_interface_bulk_data['data'][key]['value']) + + for key in sfp_compliance_code_tup: + if key in sfp_interface_bulk_data['data']['Specification compliance']['value']: + compliance_code_dict[key] = sfp_interface_bulk_data['data']['Specification compliance']['value'][key]['value'] + + transceiver_info_dict['specification_compliance'] = str( + compliance_code_dict) + transceiver_info_dict['nominal_bit_rate'] = str( + sfp_interface_bulk_data['data']['NominalSignallingRate(UnitsOf100Mbd)']['value']) + else: + for key in qsfp_cable_length_tup: + if key in sfp_interface_bulk_data['data']: + transceiver_info_dict['cable_type'] = key + transceiver_info_dict['cable_length'] = str( + sfp_interface_bulk_data['data'][key]['value']) + + for key in qsfp_compliance_code_tup: + if key in sfp_interface_bulk_data['data']['Specification compliance']['value']: + compliance_code_dict[key] = sfp_interface_bulk_data['data']['Specification compliance']['value'][key]['value'] + + transceiver_info_dict['specification_compliance'] = str( + compliance_code_dict) + transceiver_info_dict['nominal_bit_rate'] = str( + sfp_interface_bulk_data['data']['Nominal Bit Rate(100Mbs)']['value']) + + + return transceiver_info_dict + + def get_transceiver_bulk_status(self): + """ + Retrieves transceiver bulk status of this SFP + Returns: + A dict which contains following keys/values : + ======================================================================== + keys |Value Format |Information + ---------------------------|---------------|---------------------------- + rx_los |BOOLEAN |RX loss-of-signal status, True if has RX los, False if not. + tx_fault |BOOLEAN |TX fault status, True if has TX fault, False if not. + reset_status |BOOLEAN |reset status, True if SFP in reset, False if not. + lp_mode |BOOLEAN |low power mode status, True in lp mode, False if not. + tx_disable |BOOLEAN |TX disable status, True TX disabled, False if not. + tx_disabled_channel |HEX |disabled TX channels in hex, bits 0 to 3 represent channel 0 + | |to channel 3. + temperature |INT |module temperature in Celsius + voltage |INT |supply voltage in mV + txbias |INT |TX Bias Current in mA, n is the channel number, + | |for example, tx2bias stands for tx bias of channel 2. + rxpower |INT |received optical power in mW, n is the channel number, + | |for example, rx2power stands for rx power of channel 2. + txpower |INT |TX output power in mW, n is the channel number, + | |for example, tx2power stands for tx power of channel 2. + ======================================================================== + """ + # check present status + if self.port_num < 49: #SFP case + sfpd_obj = sff8472Dom() + if not self.get_presence() or not sfpd_obj: + return {} + + eeprom_ifraw = self.__read_eeprom_specific_bytes(0, SFP_DOM_OFFSET) + sfpi_obj = sff8472InterfaceId(eeprom_ifraw) + cal_type = sfpi_obj.get_calibration_type() + sfpd_obj._calibration_type = cal_type + + offset = SFP_DOM_OFFSET + transceiver_dom_info_dict = dict.fromkeys(self.dom_dict_keys, 'N/A') + dom_temperature_raw = self.__read_eeprom_specific_bytes( + (offset + SFP_TEMPE_OFFSET), SFP_TEMPE_WIDTH) + + if dom_temperature_raw is not None: + dom_temperature_data = sfpd_obj.parse_temperature( + dom_temperature_raw, 0) + transceiver_dom_info_dict['temperature'] = dom_temperature_data['data']['Temperature']['value'] + + dom_voltage_raw = self.__read_eeprom_specific_bytes( + (offset + SFP_VOLT_OFFSET), SFP_VOLT_WIDTH) + if dom_voltage_raw is not None: + dom_voltage_data = sfpd_obj.parse_voltage(dom_voltage_raw, 0) + transceiver_dom_info_dict['voltage'] = dom_voltage_data['data']['Vcc']['value'] + + dom_channel_monitor_raw = self.__read_eeprom_specific_bytes( + (offset + SFP_CHANNL_MON_OFFSET), SFP_CHANNL_MON_WIDTH) + if dom_channel_monitor_raw is not None: + dom_voltage_data = sfpd_obj.parse_channel_monitor_params( + dom_channel_monitor_raw, 0) + transceiver_dom_info_dict['tx1power'] = dom_voltage_data['data']['TXPower']['value'] + transceiver_dom_info_dict['rx1power'] = dom_voltage_data['data']['RXPower']['value'] + transceiver_dom_info_dict['tx1bias'] = dom_voltage_data['data']['TXBias']['value'] + + else: #QSFP case + sfpd_obj = sff8436Dom() + sfpi_obj = sff8436InterfaceId() + + if not self.get_presence() or not sfpi_obj or not sfpd_obj: + return {} + + transceiver_dom_info_dict = dict.fromkeys(self.dom_dict_keys, 'N/A') + offset = QSFP_DOM_OFFSET + offset_xcvr = QSFP_INFO_OFFSET + + # QSFP capability byte parse, through this byte can know whether it support tx_power or not. + # TODO: in the future when decided to migrate to support SFF-8636 instead of SFF-8436, + # need to add more code for determining the capability and version compliance + # in SFF-8636 dom capability definitions evolving with the versions. + qsfp_dom_capability_raw = self.__read_eeprom_specific_bytes( + (offset_xcvr + XCVR_DOM_CAPABILITY_OFFSET), XCVR_DOM_CAPABILITY_WIDTH) + if qsfp_dom_capability_raw is not None: + qspf_dom_capability_data = sfpi_obj.parse_dom_capability( + qsfp_dom_capability_raw, 0) + else: + return None + + dom_temperature_raw = self.__read_eeprom_specific_bytes( + (offset + QSFP_TEMPE_OFFSET), QSFP_TEMPE_WIDTH) + if dom_temperature_raw is not None: + dom_temperature_data = sfpd_obj.parse_temperature( + dom_temperature_raw, 0) + transceiver_dom_info_dict['temperature'] = dom_temperature_data['data']['Temperature']['value'] + + dom_voltage_raw = self.__read_eeprom_specific_bytes( + (offset + QSFP_VOLT_OFFSET), QSFP_VOLT_WIDTH) + if dom_voltage_raw is not None: + dom_voltage_data = sfpd_obj.parse_voltage(dom_voltage_raw, 0) + transceiver_dom_info_dict['voltage'] = dom_voltage_data['data']['Vcc']['value'] + + qsfp_dom_rev_raw = self.__read_eeprom_specific_bytes( + (offset + QSFP_DOM_REV_OFFSET), QSFP_DOM_REV_WIDTH) + if qsfp_dom_rev_raw is not None: + qsfp_dom_rev_data = sfpd_obj.parse_sfp_dom_rev(qsfp_dom_rev_raw, 0) + qsfp_dom_rev = qsfp_dom_rev_data['data']['dom_rev']['value'] + + # The tx_power monitoring is only available on QSFP which compliant with SFF-8636 + # and claimed that it support tx_power with one indicator bit. + dom_channel_monitor_data = {} + dom_channel_monitor_raw = None + qsfp_tx_power_support = qspf_dom_capability_data['data']['Tx_power_support']['value'] + if (qsfp_dom_rev[0:8] != 'SFF-8636' or (qsfp_dom_rev[0:8] == 'SFF-8636' and qsfp_tx_power_support != 'on')): + dom_channel_monitor_raw = self.__read_eeprom_specific_bytes( + (offset + QSFP_CHANNL_MON_OFFSET), QSFP_CHANNL_MON_WIDTH) + if dom_channel_monitor_raw is not None: + dom_channel_monitor_data = sfpd_obj.parse_channel_monitor_params( + dom_channel_monitor_raw, 0) + + else: + dom_channel_monitor_raw = self.__read_eeprom_specific_bytes( + (offset + QSFP_CHANNL_MON_OFFSET), QSFP_CHANNL_MON_WITH_TX_POWER_WIDTH) + if dom_channel_monitor_raw is not None: + dom_channel_monitor_data = sfpd_obj.parse_channel_monitor_params_with_tx_power( + dom_channel_monitor_raw, 0) + transceiver_dom_info_dict['tx1power'] = dom_channel_monitor_data['data']['TX1Power']['value'] + transceiver_dom_info_dict['tx2power'] = dom_channel_monitor_data['data']['TX2Power']['value'] + transceiver_dom_info_dict['tx3power'] = dom_channel_monitor_data['data']['TX3Power']['value'] + transceiver_dom_info_dict['tx4power'] = dom_channel_monitor_data['data']['TX4Power']['value'] + + if dom_channel_monitor_raw: + transceiver_dom_info_dict['rx1power'] = dom_channel_monitor_data['data']['RX1Power']['value'] + transceiver_dom_info_dict['rx2power'] = dom_channel_monitor_data['data']['RX2Power']['value'] + transceiver_dom_info_dict['rx3power'] = dom_channel_monitor_data['data']['RX3Power']['value'] + transceiver_dom_info_dict['rx4power'] = dom_channel_monitor_data['data']['RX4Power']['value'] + transceiver_dom_info_dict['tx1bias'] = dom_channel_monitor_data['data']['TX1Bias']['value'] + transceiver_dom_info_dict['tx2bias'] = dom_channel_monitor_data['data']['TX2Bias']['value'] + transceiver_dom_info_dict['tx3bias'] = dom_channel_monitor_data['data']['TX3Bias']['value'] + transceiver_dom_info_dict['tx4bias'] = dom_channel_monitor_data['data']['TX4Bias']['value'] + #End of else + + + for key in transceiver_dom_info_dict: + transceiver_dom_info_dict[key] = self._convert_string_to_num( + transceiver_dom_info_dict[key]) + + transceiver_dom_info_dict['rx_los'] = self.get_rx_los() + transceiver_dom_info_dict['tx_fault'] = self.get_tx_fault() + transceiver_dom_info_dict['reset_status'] = self.get_reset_status() + transceiver_dom_info_dict['lp_mode'] = self.get_lpmode() + + return transceiver_dom_info_dict + + def get_transceiver_threshold_info(self): + """ + Retrieves transceiver threshold info of this SFP + Returns: + A dict which contains following keys/values : + ======================================================================== + keys |Value Format |Information + ---------------------------|---------------|---------------------------- + temphighalarm |FLOAT |High Alarm Threshold value of temperature in Celsius. + templowalarm |FLOAT |Low Alarm Threshold value of temperature in Celsius. + temphighwarning |FLOAT |High Warning Threshold value of temperature in Celsius. + templowwarning |FLOAT |Low Warning Threshold value of temperature in Celsius. + vcchighalarm |FLOAT |High Alarm Threshold value of supply voltage in mV. + vcclowalarm |FLOAT |Low Alarm Threshold value of supply voltage in mV. + vcchighwarning |FLOAT |High Warning Threshold value of supply voltage in mV. + vcclowwarning |FLOAT |Low Warning Threshold value of supply voltage in mV. + rxpowerhighalarm |FLOAT |High Alarm Threshold value of received power in dBm. + rxpowerlowalarm |FLOAT |Low Alarm Threshold value of received power in dBm. + rxpowerhighwarning |FLOAT |High Warning Threshold value of received power in dBm. + rxpowerlowwarning |FLOAT |Low Warning Threshold value of received power in dBm. + txpowerhighalarm |FLOAT |High Alarm Threshold value of transmit power in dBm. + txpowerlowalarm |FLOAT |Low Alarm Threshold value of transmit power in dBm. + txpowerhighwarning |FLOAT |High Warning Threshold value of transmit power in dBm. + txpowerlowwarning |FLOAT |Low Warning Threshold value of transmit power in dBm. + txbiashighalarm |FLOAT |High Alarm Threshold value of tx Bias Current in mA. + txbiaslowalarm |FLOAT |Low Alarm Threshold value of tx Bias Current in mA. + txbiashighwarning |FLOAT |High Warning Threshold value of tx Bias Current in mA. + txbiaslowwarning |FLOAT |Low Warning Threshold value of tx Bias Current in mA. + ======================================================================== + """ + # check present status + if self.port_num < 49: + sfpd_obj = sff8472Dom() + + if not self.get_presence() and not sfpd_obj: + return {} + + eeprom_ifraw = self.__read_eeprom_specific_bytes(0, SFP_DOM_OFFSET) + sfpi_obj = sff8472InterfaceId(eeprom_ifraw) + cal_type = sfpi_obj.get_calibration_type() + sfpd_obj._calibration_type = cal_type + + offset = SFP_DOM_OFFSET + transceiver_dom_threshold_info_dict = dict.fromkeys( + self.threshold_dict_keys, 'N/A') + dom_module_threshold_raw = self.__read_eeprom_specific_bytes( + (offset + SFP_MODULE_THRESHOLD_OFFSET), SFP_MODULE_THRESHOLD_WIDTH) + if dom_module_threshold_raw is not None: + dom_module_threshold_data = sfpd_obj.parse_alarm_warning_threshold( + dom_module_threshold_raw, 0) + + transceiver_dom_threshold_info_dict['temphighalarm'] = dom_module_threshold_data['data']['TempHighAlarm']['value'] + transceiver_dom_threshold_info_dict['templowalarm'] = dom_module_threshold_data['data']['TempLowAlarm']['value'] + transceiver_dom_threshold_info_dict['temphighwarning'] = dom_module_threshold_data['data']['TempHighWarning']['value'] + transceiver_dom_threshold_info_dict['templowwarning'] = dom_module_threshold_data['data']['TempLowWarning']['value'] + transceiver_dom_threshold_info_dict['vcchighalarm'] = dom_module_threshold_data['data']['VoltageHighAlarm']['value'] + transceiver_dom_threshold_info_dict['vcclowalarm'] = dom_module_threshold_data['data']['VoltageLowAlarm']['value'] + transceiver_dom_threshold_info_dict['vcchighwarning'] = dom_module_threshold_data[ + 'data']['VoltageHighWarning']['value'] + transceiver_dom_threshold_info_dict['vcclowwarning'] = dom_module_threshold_data['data']['VoltageLowWarning']['value'] + transceiver_dom_threshold_info_dict['txbiashighalarm'] = dom_module_threshold_data['data']['BiasHighAlarm']['value'] + transceiver_dom_threshold_info_dict['txbiaslowalarm'] = dom_module_threshold_data['data']['BiasLowAlarm']['value'] + transceiver_dom_threshold_info_dict['txbiashighwarning'] = dom_module_threshold_data['data']['BiasHighWarning']['value'] + transceiver_dom_threshold_info_dict['txbiaslowwarning'] = dom_module_threshold_data['data']['BiasLowWarning']['value'] + transceiver_dom_threshold_info_dict['txpowerhighalarm'] = dom_module_threshold_data['data']['TXPowerHighAlarm']['value'] + transceiver_dom_threshold_info_dict['txpowerlowalarm'] = dom_module_threshold_data['data']['TXPowerLowAlarm']['value'] + transceiver_dom_threshold_info_dict['txpowerhighwarning'] = dom_module_threshold_data['data']['TXPowerHighWarning']['value'] + transceiver_dom_threshold_info_dict['txpowerlowwarning'] = dom_module_threshold_data['data']['TXPowerLowWarning']['value'] + transceiver_dom_threshold_info_dict['rxpowerhighalarm'] = dom_module_threshold_data['data']['RXPowerHighAlarm']['value'] + transceiver_dom_threshold_info_dict['rxpowerlowalarm'] = dom_module_threshold_data['data']['RXPowerLowAlarm']['value'] + transceiver_dom_threshold_info_dict['rxpowerhighwarning'] = dom_module_threshold_data['data']['RXPowerHighWarning']['value'] + transceiver_dom_threshold_info_dict['rxpowerlowwarning'] = dom_module_threshold_data['data']['RXPowerLowWarning']['value'] + + for key in transceiver_dom_threshold_info_dict: + transceiver_dom_threshold_info_dict[key] = self._convert_string_to_num( + transceiver_dom_threshold_info_dict[key]) + + return transceiver_dom_threshold_info_dict + + + else: + sfpd_obj = sff8436Dom() + + if not self.get_presence() or not sfpd_obj: + return {} + + transceiver_dom_threshold_dict = dict.fromkeys( + self.threshold_dict_keys, 'N/A') + dom_thres_raw = self.__read_eeprom_specific_bytes( + QSFP_MODULE_THRESHOLD_OFFSET, QSFP_MODULE_THRESHOLD_WIDTH) if self.get_presence() and sfpd_obj else None + + if dom_thres_raw: + module_threshold_values = sfpd_obj.parse_module_threshold_values( + dom_thres_raw, 0) + module_threshold_data = module_threshold_values.get('data') + if module_threshold_data: + transceiver_dom_threshold_dict['temphighalarm'] = module_threshold_data['TempHighAlarm']['value'] + transceiver_dom_threshold_dict['templowalarm'] = module_threshold_data['TempLowAlarm']['value'] + transceiver_dom_threshold_dict['temphighwarning'] = module_threshold_data['TempHighWarning']['value'] + transceiver_dom_threshold_dict['templowwarning'] = module_threshold_data['TempLowWarning']['value'] + transceiver_dom_threshold_dict['vcchighalarm'] = module_threshold_data['VccHighAlarm']['value'] + transceiver_dom_threshold_dict['vcclowalarm'] = module_threshold_data['VccLowAlarm']['value'] + transceiver_dom_threshold_dict['vcchighwarning'] = module_threshold_data['VccHighWarning']['value'] + transceiver_dom_threshold_dict['vcclowwarning'] = module_threshold_data['VccLowWarning']['value'] + + dom_thres_raw = self.__read_eeprom_specific_bytes( + QSFP_CHANNEL_THRESHOLD_OFFSET, QSFP_CHANNEL_THRESHOLD_WIDTH) if self.get_presence() and sfpd_obj else None + channel_threshold_values = sfpd_obj.parse_channel_threshold_values( + dom_thres_raw, 0) + channel_threshold_data = channel_threshold_values.get('data') + if channel_threshold_data: + transceiver_dom_threshold_dict['rxpowerhighalarm'] = channel_threshold_data['RxPowerHighAlarm']['value'] + transceiver_dom_threshold_dict['rxpowerlowalarm'] = channel_threshold_data['RxPowerLowAlarm']['value'] + transceiver_dom_threshold_dict['rxpowerhighwarning'] = channel_threshold_data['RxPowerHighWarning']['value'] + transceiver_dom_threshold_dict['rxpowerlowwarning'] = channel_threshold_data['RxPowerLowWarning']['value'] + transceiver_dom_threshold_dict['txpowerhighalarm'] = "0.0dBm" + transceiver_dom_threshold_dict['txpowerlowalarm'] = "0.0dBm" + transceiver_dom_threshold_dict['txpowerhighwarning'] = "0.0dBm" + transceiver_dom_threshold_dict['txpowerlowwarning'] = "0.0dBm" + transceiver_dom_threshold_dict['txbiashighalarm'] = channel_threshold_data['TxBiasHighAlarm']['value'] + transceiver_dom_threshold_dict['txbiaslowalarm'] = channel_threshold_data['TxBiasLowAlarm']['value'] + transceiver_dom_threshold_dict['txbiashighwarning'] = channel_threshold_data['TxBiasHighWarning']['value'] + transceiver_dom_threshold_dict['txbiaslowwarning'] = channel_threshold_data['TxBiasLowWarning']['value'] + + for key in transceiver_dom_threshold_dict: + transceiver_dom_threshold_dict[key] = self._convert_string_to_num( + transceiver_dom_threshold_dict[key]) + + return transceiver_dom_threshold_dict + + def get_reset_status(self): + """ + Retrieves the reset status of SFP + Returns: + A Boolean, True if reset enabled, False if disabled + """ + if self.port_num <49: + return False # SPF port doesn't support this feature + + cpld_i = self.__get_cpld_num(self.port_num) + cpld_path = self._cpld_mapping[cpld_i] + reset_path = "{}{}{}{}".format(CPLD_I2C_PATH, cpld_path, '/module_reset_', self.port_num) + val=self._api_helper.read_txt_file(reset_path) + if val is not None: + return int(val, 10)==1 + else: + return False + + def get_rx_los(self): + """ + Retrieves the RX LOS (lost-of-signal) status of SFP + Returns: + A Boolean, True if SFP has RX LOS, False if not. + Note : RX LOS status is latched until a call to get_rx_los or a reset. + """ + rx_los = False + if self.port_num < 49: + cpld_i = self.__get_cpld_num(self.port_num) + cpld_path = self._cpld_mapping[cpld_i] + rx_path = "{}{}{}{}".format(CPLD_I2C_PATH, cpld_path, '/module_rx_los_', self.port_num) + + rx_los=self._api_helper.read_txt_file(rx_path) + if rx_los is None: + return False + #status_control_raw = self.__read_eeprom_specific_bytes( + # SFP_STATUS_CONTROL_OFFSET, SFP_STATUS_CONTROL_WIDTH) + #if status_control_raw: + # data = int(status_control_raw[0], 16) + # rx_los = (sffbase().test_bit(data, 1) != 0) + + else: + rx_los_list = [] + dom_channel_monitor_raw = self.__read_eeprom_specific_bytes( + QSFP_CHANNL_RX_LOS_STATUS_OFFSET, QSFP_CHANNL_RX_LOS_STATUS_WIDTH) if self.get_presence() else None + if dom_channel_monitor_raw is not None: + rx_los_data = int(dom_channel_monitor_raw[0], 16) + rx_los_list.append(rx_los_data & 0x01 != 0) + rx_los_list.append(rx_los_data & 0x02 != 0) + rx_los_list.append(rx_los_data & 0x04 != 0) + rx_los_list.append(rx_los_data & 0x08 != 0) + rx_los = rx_los_list[0] and rx_los_list[1] and rx_los_list[2] and rx_los_list[3] + + return rx_los + + def get_tx_fault(self): + """ + Retrieves the TX fault status of SFP + Returns: + A Boolean, True if SFP has TX fault, False if not + Note : TX fault status is lached until a call to get_tx_fault or a reset. + """ + tx_fault = False + if self.port_num < 49: + cpld_i = self.__get_cpld_num(self.port_num) + cpld_path = self._cpld_mapping[cpld_i] + tx_path = "{}{}{}{}".format(CPLD_I2C_PATH, cpld_path, '/module_tx_fault_', self.port_num) + + tx_fault=self._api_helper.read_txt_file(tx_path) + if tx_fault is None: + return False + #status_control_raw = self.__read_eeprom_specific_bytes( + # SFP_STATUS_CONTROL_OFFSET, SFP_STATUS_CONTROL_WIDTH) + #if status_control_raw: + # data = int(status_control_raw[0], 16) + # tx_fault = (sffbase().test_bit(data, 2) != 0) + else: + tx_fault_list = [] + dom_channel_monitor_raw = self.__read_eeprom_specific_bytes( + QSFP_CHANNL_TX_FAULT_STATUS_OFFSET, QSFP_CHANNL_TX_FAULT_STATUS_WIDTH) if self.get_presence() else None + if dom_channel_monitor_raw is not None: + tx_fault_data = int(dom_channel_monitor_raw[0], 16) + tx_fault_list.append(tx_fault_data & 0x01 != 0) + tx_fault_list.append(tx_fault_data & 0x02 != 0) + tx_fault_list.append(tx_fault_data & 0x04 != 0) + tx_fault_list.append(tx_fault_data & 0x08 != 0) + tx_fault = tx_fault_list[0] and tx_fault_list[1] and tx_fault_list[2] and tx_fault_list[3] + + return tx_fault + + def get_tx_disable(self): + """ + Retrieves the tx_disable status of this SFP + Returns: + A Boolean, True if tx_disable is enabled, False if disabled + """ + if self.port_num < 49: + tx_disable = False + + cpld_i = self.__get_cpld_num(self.port_num) + cpld_path = self._cpld_mapping[cpld_i] + tx_path = "{}{}{}{}".format(CPLD_I2C_PATH, cpld_path, '/module_tx_disable_', self.port_num) + + tx_disable=self._api_helper.read_txt_file(tx_path) + + #status_control_raw = self.__read_eeprom_specific_bytes( + # SFP_STATUS_CONTROL_OFFSET, SFP_STATUS_CONTROL_WIDTH) + #if status_control_raw: + # data = int(status_control_raw[0], 16) + # tx_disable_hard = (sffbase().test_bit( + # data, SFP_TX_DISABLE_HARD_BIT) != 0) + # tx_disable_soft = (sffbase().test_bit( + # data, SFP_TX_DISABLE_SOFT_BIT) != 0) + # tx_disable = tx_disable_hard | tx_disable_soft + if tx_disable is not None: + return tx_disable + else: + return False + else: + tx_disable_list = [] + + sfpd_obj = sff8436Dom() + if sfpd_obj is None: + return False + + dom_control_raw = self.__read_eeprom_specific_bytes( + QSFP_CONTROL_OFFSET, QSFP_CONTROL_WIDTH) if self.get_presence() else None + if dom_control_raw is not None: + dom_control_data = sfpd_obj.parse_control_bytes(dom_control_raw, 0) + tx_disable_list.append( + 'On' == dom_control_data['data']['TX1Disable']['value']) + tx_disable_list.append( + 'On' == dom_control_data['data']['TX2Disable']['value']) + tx_disable_list.append( + 'On' == dom_control_data['data']['TX3Disable']['value']) + tx_disable_list.append( + 'On' == dom_control_data['data']['TX4Disable']['value']) + + return tx_disable_list + + def get_tx_disable_channel(self): + """ + Retrieves the TX disabled channels in this SFP + Returns: + A hex of 4 bits (bit 0 to bit 3 as channel 0 to channel 3) to represent + TX channels which have been disabled in this SFP. + As an example, a returned value of 0x5 indicates that channel 0 + and channel 2 have been disabled. + """ + if self.port_num < 49: + # SFP doesn't support this feature + return False + else: + tx_disable_list = self.get_tx_disable() + if tx_disable_list is None: + return 0 + tx_disabled = 0 + for i in range(len(tx_disable_list)): + if tx_disable_list[i]: + tx_disabled |= 1 << i + return tx_disabled + + def get_lpmode(self): + """ + Retrieves the lpmode (low power mode) status of this SFP + Returns: + A Boolean, True if lpmode is enabled, False if disabled + """ + if self.port_num < 49: + # SFP doesn't support this feature + return False + else: + power_set=self.get_power_set() + power_override = self.get_power_override() + return power_set and power_override + + + def get_power_set(self): + + if self.port_num < 49: + # SFP doesn't support this feature + return False + else: + power_set = False + + sfpd_obj = sff8436Dom() + if sfpd_obj is None: + return False + + dom_control_raw = self.__read_eeprom_specific_bytes( + QSFP_CONTROL_OFFSET, QSFP_CONTROL_WIDTH) if self.get_presence() else None + if dom_control_raw is not None: + dom_control_data = sfpd_obj.parse_control_bytes(dom_control_raw, 0) + power_set = ( + 'On' == dom_control_data['data']['PowerSet']['value']) + + return power_set + + def get_power_override(self): + """ + Retrieves the power-override status of this SFP + Returns: + A Boolean, True if power-override is enabled, False if disabled + """ + if self.port_num < 49: + return False # SFP doesn't support this feature + else: + power_override = False + + + sfpd_obj = sff8436Dom() + if sfpd_obj is None: + return False + + dom_control_raw = self.__read_eeprom_specific_bytes( + QSFP_CONTROL_OFFSET, QSFP_CONTROL_WIDTH) if self.get_presence() else None + if dom_control_raw is not None: + dom_control_data = sfpd_obj.parse_control_bytes(dom_control_raw, 0) + power_override = ( + 'On' == dom_control_data['data']['PowerOverride']['value']) + + return power_override + + def get_temperature(self): + """ + Retrieves the temperature of this SFP + Returns: + An integer number of current temperature in Celsius + """ + transceiver_dom_info_dict = self.get_transceiver_bulk_status() + return transceiver_dom_info_dict.get("temperature", "N/A") + + def get_voltage(self): + """ + Retrieves the supply voltage of this SFP + Returns: + An integer number of supply voltage in mV + """ + transceiver_dom_info_dict = self.get_transceiver_bulk_status() + return transceiver_dom_info_dict.get("voltage", "N/A") + + def get_tx_bias(self): + """ + Retrieves the TX bias current of this SFP + Returns: + A list of four integer numbers, representing TX bias in mA + for channel 0 to channel 4. + Ex. ['110.09', '111.12', '108.21', '112.09'] + """ + transceiver_dom_info_dict = self.get_transceiver_bulk_status() + + tx1_bs = transceiver_dom_info_dict.get("tx1bias", "N/A") + if self.port_num < 49: + return [tx1_bs, "N/A", "N/A", "N/A"] if transceiver_dom_info_dict else [] + + tx2_bs = transceiver_dom_info_dict.get("tx2bias", "N/A") + tx3_bs = transceiver_dom_info_dict.get("tx3bias", "N/A") + tx4_bs = transceiver_dom_info_dict.get("tx4bias", "N/A") + return [tx1_bs, tx2_bs, tx3_bs, tx4_bs] if transceiver_dom_info_dict else [] + + def get_rx_power(self): + """ + Retrieves the received optical power for this SFP + Returns: + A list of four integer numbers, representing received optical + power in mW for channel 0 to channel 4. + Ex. ['1.77', '1.71', '1.68', '1.70'] + """ + transceiver_dom_info_dict = self.get_transceiver_bulk_status() + + rx1_pw = transceiver_dom_info_dict.get("rx1power", "N/A") + if self.port_num < 49: + return [rx1_pw, "N/A", "N/A", "N/A"] if transceiver_dom_info_dict else [] + rx2_pw = transceiver_dom_info_dict.get("rx2power", "N/A") + rx3_pw = transceiver_dom_info_dict.get("rx3power", "N/A") + rx4_pw = transceiver_dom_info_dict.get("rx4power", "N/A") + return [rx1_pw, rx2_pw, rx3_pw, rx4_pw] if transceiver_dom_info_dict else [] + + def get_tx_power(self): + """ + Retrieves the TX power of this SFP + Returns: + A list of four integer numbers, representing TX power in mW + for channel 0 to channel 4. + Ex. ['1.86', '1.86', '1.86', '1.86'] + """ + transceiver_dom_info_dict = self.get_transceiver_bulk_status() + tx1_pw = transceiver_dom_info_dict.get("tx1power", "N/A") + if self.port_num < 49: + return [tx1_pw, "N/A", "N/A", "N/A"] if transceiver_dom_info_dict else [] + tx2_pw = transceiver_dom_info_dict.get("tx2power", "N/A") + tx3_pw = transceiver_dom_info_dict.get("tx3power", "N/A") + tx4_pw = transceiver_dom_info_dict.get("tx4power", "N/A") + return [tx1_pw, tx2_pw, tx3_pw, tx4_pw] + + def reset(self): + """ + Reset SFP and return all user module settings to their default srate. + Returns: + A boolean, True if successful, False if not + """ + if self.port_num <49: + return False # SFP doesn't support this feature + + cpld_i = self.__get_cpld_num(self.port_num) + cpld_path = self._cpld_mapping[cpld_i] + reset_path = "{}{}{}{}".format(CPLD_I2C_PATH, cpld_path, '/module_reset_', self.port_num) + ret = self.__write_txt_file(reset_path, 1) + + if ret is not True: + time.sleep(0.01) + ret = self.__write_txt_file(reset_path, 0) + time.sleep(0.2) + return ret + else: + return False + + + def tx_disable(self, tx_disable): + """ + Disable SFP TX for all channels + Args: + tx_disable : A Boolean, True to enable tx_disable mode, False to disable + tx_disable mode. + Returns: + A boolean, True if tx_disable is set successfully, False if not + """ + if self.port_num < 49: + cpld_i = self.__get_cpld_num(self.port_num) + cpld_path = self._cpld_mapping[cpld_i] + tx_path = "{}{}{}{}".format(CPLD_I2C_PATH, cpld_path, '/module_tx_disable_', self.port_num) + ret = self.__write_txt_file(tx_path, 1 if tx_disable else 0) + + if ret is not None: + time.sleep(0.01) + return ret + else: + return False + + else: + if not self.get_presence(): + return False + sysfsfile_eeprom = None + try: + tx_disable_ctl = 0xf if tx_disable else 0x0 + buffer = create_string_buffer(1) + if sys.version_info[0] >= 3: + buffer[0] = tx_disable_ctl + else: + buffer[0] = chr(tx_disable_ctl) + # Write to eeprom + sysfsfile_eeprom = open( + self.port_to_eeprom_mapping[self.port_num], "r+b") + sysfsfile_eeprom.seek(QSFP_CONTROL_OFFSET) + + sysfsfile_eeprom.write(buffer[0]) + except IOError as e: + print ('Error: unable to open file: ',str(e)) + return False + finally: + if sysfsfile_eeprom is not None: + sysfsfile_eeprom.close() + time.sleep(0.01) + return True + + def tx_disable_channel(self, channel, disable): + """ + Sets the tx_disable for specified SFP channels + Args: + channel : A hex of 4 bits (bit 0 to bit 3) which represent channel 0 to 3, + e.g. 0x5 for channel 0 and channel 2. + disable : A boolean, True to disable TX channels specified in channel, + False to enable + Returns: + A boolean, True if successful, False if not + """ + + if self.port_num < 49: + return False # SFP doesn't support this feature + else: + if not self.get_presence(): + return False + + sysfsfile_eeprom = None + try: + channel_state = self.get_tx_disable_channel() + + for i in range(4): + channel_mask = (1 << i) + if not (channel & channel_mask): + continue + + if disable: + channel_state |= channel_mask + else: + channel_state &= ~channel_mask + + buffer = create_string_buffer(1) + if sys.version_info[0] >= 3: + buffer[0] = channel_state + else: + buffer[0] = chr(channel_state) + # Write to eeprom + sysfsfile_eeprom = open( + self.port_to_eeprom_mapping[self.port_num], "r+b") + sysfsfile_eeprom.seek(QSFP_CONTROL_OFFSET) + sysfsfile_eeprom.write(buffer[0]) + except IOError as e: + print ('Error: unable to open file: ', str(e)) + return False + finally: + if sysfsfile_eeprom is not None: + sysfsfile_eeprom.close() + time.sleep(0.01) + return True + + def set_lpmode(self, lpmode): + """ + Sets the lpmode (low power mode) of SFP + Args: + lpmode: A Boolean, True to enable lpmode, False to disable it + Note : lpmode can be overridden by set_power_override + Returns: + A boolean, True if lpmode is set successfully, False if not + """ + if self.port_num < 49: + return False # SFP doesn't support this feature + else: + if lpmode is True: + self.set_power_override(True, True) + else: + self.set_power_override(False, False) + + return True + + def set_power_override(self, power_override, power_set): + """ + Sets SFP power level using power_override and power_set + Args: + power_override : + A Boolean, True to override set_lpmode and use power_set + to control SFP power, False to disable SFP power control + through power_override/power_set and use set_lpmode + to control SFP power. + power_set : + Only valid when power_override is True. + A Boolean, True to set SFP to low power mode, False to set + SFP to high power mode. + Returns: + A boolean, True if power-override and power_set are set successfully, + False if not + """ + if self.port_num < 49: + return False # SFP doesn't support this feature + else: + if not self.get_presence(): + return False + try: + power_override_bit = (1 << 0) if power_override else 0 + power_set_bit = (1 << 1) if power_set else (1 << 3) + + buffer = create_string_buffer(1) + if sys.version_info[0] >= 3: + buffer[0] = (power_override_bit | power_set_bit) + else: + buffer[0] = chr(power_override_bit | power_set_bit) + # Write to eeprom + with open(self.port_to_eeprom_mapping[self.port_num], "r+b") as fd: + fd.seek(QSFP_POWEROVERRIDE_OFFSET) + fd.write(buffer[0]) + time.sleep(0.01) + except Exception: + print ('Error: unable to open file: ', str(e)) + return False + return True + + def get_name(self): + """ + Retrieves the name of the device + Returns: + string: The name of the device + """ + sfputil_helper = SfpUtilHelper() + sfputil_helper.read_porttab_mappings( + self.__get_path_to_port_config_file()) + name = sfputil_helper.logical[self.index] or "Unknown" + return name + + def get_presence(self): + """ + Retrieves the presence of the device + Returns: + bool: True if device is present, False if not + """ + cpld_i = self.__get_cpld_num(self.port_num) + cpld_path = self._cpld_mapping[cpld_i] + present_path = "{}{}{}{}".format(CPLD_I2C_PATH, cpld_path, '/module_present_', self.port_num) + val=self._api_helper.read_txt_file(present_path) + if val is not None: + return int(val, 10)==1 + else: + return False + + def get_model(self): + """ + Retrieves the model number (or part number) of the device + Returns: + string: Model/part number of device + """ + transceiver_dom_info_dict = self.get_transceiver_info() + return transceiver_dom_info_dict.get("model", "N/A") + + def get_serial(self): + """ + Retrieves the serial number of the device + Returns: + string: Serial number of device + """ + transceiver_dom_info_dict = self.get_transceiver_info() + return transceiver_dom_info_dict.get("serial", "N/A") + + def get_status(self): + """ + Retrieves the operational status of the device + Returns: + A boolean value, True if device is operating properly, False if not + """ + return self.get_presence() and self.get_transceiver_bulk_status() diff --git a/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/thermal.py b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/thermal.py new file mode 100644 index 00000000000..d5269ec2b12 --- /dev/null +++ b/device/accton/x86_64-accton_as5835_54x-r0/sonic_platform/thermal.py @@ -0,0 +1,148 @@ +############################################################################# +# Edgecore +# +# Thermal contains an implementation of SONiC Platform Base API and +# provides the thermal device status which are available in the platform +# +############################################################################# + +import os +import os.path +import glob + +try: + from sonic_platform_base.thermal_base import ThermalBase +except ImportError as e: + raise ImportError(str(e) + "- required module not found") + + +class Thermal(ThermalBase): + """Platform-specific Thermal class""" + + THERMAL_NAME_LIST = [] + SYSFS_PATH = "/sys/bus/i2c/devices" + + def __init__(self, thermal_index=0): + self.index = thermal_index + + # Add thermal name + self.THERMAL_NAME_LIST.append("Temp sensor 1") + self.THERMAL_NAME_LIST.append("Temp sensor 2") + self.THERMAL_NAME_LIST.append("Temp sensor 3") + self.THERMAL_NAME_LIST.append("Temp sensor 4") + + # Set hwmon path + i2c_path = { + 0: "18-004b/hwmon/hwmon*/", + 1: "19-004c/hwmon/hwmon*/", + 2: "20-0049/hwmon/hwmon*/", + 3: "21-004a/hwmon/hwmon*/" + }.get(self.index, None) + + self.hwmon_path = "{}/{}".format(self.SYSFS_PATH, i2c_path) + self.ss_key = self.THERMAL_NAME_LIST[self.index] + self.ss_index = 1 + + def __read_txt_file(self, file_path): + for filename in glob.glob(file_path): + try: + with open(filename, 'r') as fd: + data =fd.readline().rstrip() + return data + except IOError as e: + pass + + return None + + + def __get_temp(self, temp_file): + temp_file_path = os.path.join(self.hwmon_path, temp_file) + raw_temp = self.__read_txt_file(temp_file_path) + if raw_temp is not None: + return float(raw_temp)/1000 + else: + return 0 + + + def __set_threshold(self, file_name, temperature): + temp_file_path = os.path.join(self.hwmon_path, file_name) + for filename in glob.glob(temp_file_path): + try: + with open(filename, 'w') as fd: + fd.write(str(temperature)) + return True + except IOError as e: + print("IOError") + + + def get_temperature(self): + """ + Retrieves current temperature reading from thermal + Returns: + A float number of current temperature in Celsius up to nearest thousandth + of one degree Celsius, e.g. 30.125 + """ + temp_file = "temp{}_input".format(self.ss_index) + return self.__get_temp(temp_file) + + def get_high_threshold(self): + """ + Retrieves the high threshold temperature of thermal + Returns: + A float number, the high threshold temperature of thermal in Celsius + up to nearest thousandth of one degree Celsius, e.g. 30.125 + """ + temp_file = "temp{}_max".format(self.ss_index) + return self.__get_temp(temp_file) + + def set_high_threshold(self, temperature): + """ + Sets the high threshold temperature of thermal + Args : + temperature: A float number up to nearest thousandth of one degree Celsius, + e.g. 30.125 + Returns: + A boolean, True if threshold is set successfully, False if not + """ + temp_file = "temp{}_max".format(self.ss_index) + temperature = temperature *1000 + self.__set_threshold(temp_file, temperature) + + return True + + def get_name(self): + """ + Retrieves the name of the thermal device + Returns: + string: The name of the thermal device + """ + return self.THERMAL_NAME_LIST[self.index] + + def get_presence(self): + """ + Retrieves the presence of the Thermal + Returns: + bool: True if Thermal is present, False if not + """ + temp_file = "temp{}_input".format(self.ss_index) + temp_file_path = os.path.join(self.hwmon_path, temp_file) + raw_txt = self.__read_txt_file(temp_file_path) + if raw_txt is not None: + return True + else: + return False + + def get_status(self): + """ + Retrieves the operational status of the device + Returns: + A boolean value, True if device is operating properly, False if not + """ + + file_str = "temp{}_input".format(self.ss_index) + file_path = os.path.join(self.hwmon_path, file_str) + raw_txt = self.__read_txt_file(file_path) + if raw_txt is None: + return False + else: + return int(raw_txt) != 0 diff --git a/platform/broadcom/sonic-platform-modules-accton/as5835-54x/sonic_platform_setup.py b/platform/broadcom/sonic-platform-modules-accton/as5835-54x/sonic_platform_setup.py new file mode 100644 index 00000000000..a775511c643 --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-accton/as5835-54x/sonic_platform_setup.py @@ -0,0 +1,34 @@ +from setuptools import setup + +DEVICE_NAME = 'accton' +HW_SKU = 'x86_64-accton_as5835_54x-r0' + +setup( + name='sonic-platform', + version='1.0', + description='SONiC platform API implementation on Accton Platforms', + license='Apache 2.0', + author='SONiC Team', + author_email='linuxnetdev@microsoft.com', + url='https://github.com/Azure/sonic-buildimage', + maintainer='Jostar Yang', + maintainer_email='jostar_yang@accton.com', + packages=[ + 'sonic_platform', + ], + package_dir={ + 'sonic_platform': '../../../../device/{}/{}/sonic_platform'.format(DEVICE_NAME, HW_SKU)}, + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Environment :: Plugins', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: Apache Software License', + 'Natural Language :: English', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python :: 3.7', + 'Topic :: Utilities', + ], + keywords='sonic SONiC platform PLATFORM', +) diff --git a/platform/broadcom/sonic-platform-modules-accton/as5835-54x/utils/accton_as5835_54x_util.py b/platform/broadcom/sonic-platform-modules-accton/as5835-54x/utils/accton_as5835_54x_util.py index 738a1766fbe..63ed27eabba 100755 --- a/platform/broadcom/sonic-platform-modules-accton/as5835-54x/utils/accton_as5835_54x_util.py +++ b/platform/broadcom/sonic-platform-modules-accton/as5835-54x/utils/accton_as5835_54x_util.py @@ -36,6 +36,7 @@ import logging import re import time +import os @@ -89,6 +90,10 @@ def main(): do_install() elif arg == 'clean': do_uninstall() + elif arg == 'api': + do_sonic_platform_install() + elif arg == 'api_clean': + do_sonic_platform_clean() elif arg == 'show': device_traversal() elif arg == 'sff': @@ -364,6 +369,44 @@ def system_ready(): return False return True +PLATFORM_ROOT_PATH = '/usr/share/sonic/device' +PLATFORM_API2_WHL_FILE_PY3 ='sonic_platform-1.0-py3-none-any.whl' +def do_sonic_platform_install(): + device_path = "{}{}{}{}".format(PLATFORM_ROOT_PATH, '/x86_64-accton_', PROJECT_NAME, '-r0') + SONIC_PLATFORM_BSP_WHL_PKG_PY3 = "/".join([device_path, PLATFORM_API2_WHL_FILE_PY3]) + + #Check API2.0 on py whl file + status, output = log_os_system("pip3 show sonic-platform > /dev/null 2>&1", 0) + if status: + if os.path.exists(SONIC_PLATFORM_BSP_WHL_PKG_PY3): + status, output = log_os_system("pip3 install "+ SONIC_PLATFORM_BSP_WHL_PKG_PY3, 1) + if status: + print "Error: Failed to install {}".format(PLATFORM_API2_WHL_FILE_PY3) + return status + else: + print "Successfully installed {} package".format(PLATFORM_API2_WHL_FILE_PY3) + else: + print('{} is not found'.format(PLATFORM_API2_WHL_FILE_PY3)) + else: + print('{} has installed'.format(PLATFORM_API2_WHL_FILE_PY3)) + + return + +def do_sonic_platform_clean(): + status, output = log_os_system("pip3 show sonic-platform > /dev/null 2>&1", 0) + if status: + print('{} does not install, not need to uninstall'.format(PLATFORM_API2_WHL_FILE_PY3)) + + else: + status, output = log_os_system("pip3 uninstall sonic-platform -y", 0) + if status: + print('Error: Failed to uninstall {}'.format(PLATFORM_API2_WHL_FILE_PY3)) + return status + else: + print('{} is uninstalled'.format(PLATFORM_API2_WHL_FILE_PY3)) + + return + def do_install(): print "Checking system...." if driver_check() == False: @@ -382,6 +425,9 @@ def do_install(): return status else: print PROJECT_NAME.upper()+" devices detected...." + + do_sonic_platform_install() + return def do_uninstall(): @@ -404,6 +450,8 @@ def do_uninstall(): if FORCE == 0: return status + do_sonic_platform_clean() + return def devices_info(): diff --git a/platform/broadcom/sonic-platform-modules-accton/debian/rules b/platform/broadcom/sonic-platform-modules-accton/debian/rules index 678d649a79e..826a3217dc8 100755 --- a/platform/broadcom/sonic-platform-modules-accton/debian/rules +++ b/platform/broadcom/sonic-platform-modules-accton/debian/rules @@ -14,7 +14,7 @@ include /usr/share/dpkg/pkg-info.mk export INSTALL_MOD_DIR:=extra PYTHON ?= python2 -PYTHON3 ?= python3 +PYTHON3 ?= python3 PACKAGE_PRE_NAME := sonic-platform-accton KVERSION ?= $(shell uname -r) diff --git a/platform/broadcom/sonic-platform-modules-accton/debian/sonic-platform-accton-as5835-54x.install b/platform/broadcom/sonic-platform-modules-accton/debian/sonic-platform-accton-as5835-54x.install new file mode 100644 index 00000000000..d108eb582c3 --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-accton/debian/sonic-platform-accton-as5835-54x.install @@ -0,0 +1,2 @@ +as5835-54x/sonic_platform-1.0-py3-none-any.whl usr/share/sonic/device/x86_64-accton_as5835_54x-r0 + From 37863ac8547f159d17a8fbe5538a6b4ba38a12ad Mon Sep 17 00:00:00 2001 From: yozhao101 <56170650+yozhao101@users.noreply.github.com> Date: Fri, 28 May 2021 11:13:44 -0700 Subject: [PATCH 3/8] [Monit] Restart telemetry container if memory usage is beyond the threshold (#7645) Signed-off-by: Yong Zhao yozhao@microsoft.com Why I did it This PR aims to monitor the memory usage of streaming telemetry container and restart streaming telemetry container if memory usage is larger than the pre-defined threshold. How I did it I borrowed the system tool Monit to run a script memory_checker which will periodically check the memory usage of streaming telemetry container. If the memory usage of telemetry container is larger than the pre-defined threshold for 10 times during 20 cycles, then an alerting message will be written into syslog and at the same time Monit will run the script restart_service to restart the streaming telemetry container. How to verify it I verified this implementation on device str-7260cx3-acs-1. --- .../base_image_files/monit_telemetry | 3 + .../build_templates/sonic_debian_extension.j2 | 4 + files/image_config/monit/memory_checker | 109 ++++++++++++++++++ files/image_config/monit/restart_service | 105 +++++++++++++++++ 4 files changed, 221 insertions(+) create mode 100755 files/image_config/monit/memory_checker create mode 100755 files/image_config/monit/restart_service diff --git a/dockers/docker-sonic-telemetry/base_image_files/monit_telemetry b/dockers/docker-sonic-telemetry/base_image_files/monit_telemetry index 3680bbe6cf9..ab3000c899d 100644 --- a/dockers/docker-sonic-telemetry/base_image_files/monit_telemetry +++ b/dockers/docker-sonic-telemetry/base_image_files/monit_telemetry @@ -9,3 +9,6 @@ check program telemetry|telemetry with path "/usr/bin/process_checker telemetry check program telemetry|dialout_client with path "/usr/bin/process_checker telemetry /usr/sbin/dialout_client_cli" if status != 0 for 5 times within 5 cycles then alert repeat every 1 cycles + +check program container_memory_telemetry with path "/usr/bin/memory_checker telemetry 419430400" + if status == 3 for 10 times within 20 cycles then exec "/usr/bin/restart_service telemetry" diff --git a/files/build_templates/sonic_debian_extension.j2 b/files/build_templates/sonic_debian_extension.j2 index 274019215be..2bf752fc31d 100644 --- a/files/build_templates/sonic_debian_extension.j2 +++ b/files/build_templates/sonic_debian_extension.j2 @@ -334,6 +334,10 @@ sudo cp $IMAGE_CONFIGS/monit/process_checker $FILESYSTEM_ROOT/usr/bin/ sudo chmod 755 $FILESYSTEM_ROOT/usr/bin/process_checker sudo cp $IMAGE_CONFIGS/monit/container_checker $FILESYSTEM_ROOT/usr/bin/ sudo chmod 755 $FILESYSTEM_ROOT/usr/bin/container_checker +sudo cp $IMAGE_CONFIGS/monit/memory_checker $FILESYSTEM_ROOT/usr/bin/ +sudo chmod 755 $FILESYSTEM_ROOT/usr/bin/memory_checker +sudo cp $IMAGE_CONFIGS/monit/restart_service $FILESYSTEM_ROOT/usr/bin/ +sudo chmod 755 $FILESYSTEM_ROOT/usr/bin/restart_service # Install custom-built openssh sshd diff --git a/files/image_config/monit/memory_checker b/files/image_config/monit/memory_checker new file mode 100755 index 00000000000..4f7912c1e56 --- /dev/null +++ b/files/image_config/monit/memory_checker @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 + +""" +memory_checker + +This script is part of the feature which will restart the container if memory +usage of it is larger than the threshold value. + +This script is used to check the memory usage of specified cotnainer and +is intended to be run by Monit. It will write an alerting message into +syslog if memory usage of the container is larger than the threshold value for X +times within Y cycles/minutes. Note that if print(...) statement in this script +was executed, the string in it will be appended to Monit syslog messages. + +The following is an example in Monit configuration file to show how Monit will run +this script: + +check program container_memory_ with path "/usr/bin/memory_checker " + if status == 3 for X times within Y cycles exec "/usr/bin/restart_service " +""" + +import argparse +import subprocess +import sys +import syslog +import re + + +def get_command_result(command): + """Executes the command and return the resulting output. + + Args: + command: A string contains the command to be executed. + + Returns: + A string which contains the output of command. + """ + command_stdout = "" + + try: + proc_instance = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + shell=True, universal_newlines=True) + command_stdout, command_stderr = proc_instance.communicate() + if proc_instance.returncode != 0: + syslog.syslog(syslog.LOG_ERR, "[memory_checker] Failed to execute the command '{}'. Return code: '{}'" + .format(command, proc_instance.returncode)) + sys.exit(1) + except (OSError, ValueError) as err: + syslog.syslog(syslog.LOG_ERR, "[memory_checker] Failed to execute the command '{}'. Error: '{}'" + .format(command, err)) + sys.exit(2) + + return command_stdout.strip() + + +def check_memory_usage(container_name, threshold_value): + """Checks the memory usage of a container and writes an alerting messages into + the syslog if the memory usage is larger than the threshold value. + + Args: + container_name: A string represtents name of a container + threshold_value: An integer indicates the threshold value (Bytes) of memory usage. + + Returns: + None. + """ + command = "docker stats --no-stream --format \{{\{{.MemUsage\}}\}} {}".format(container_name) + command_stdout = get_command_result(command) + mem_usage = command_stdout.split("/")[0].strip() + match_obj = re.match(r"\d+\.?\d*", mem_usage) + if match_obj: + mem_usage_value = float(mem_usage[match_obj.start():match_obj.end()]) + mem_usage_unit = mem_usage[match_obj.end():] + + mem_usage_bytes = 0.0 + if mem_usage_unit == "B": + mem_usage_bytes = mem_usage_value + elif mem_usage_unit == "KiB": + mem_usage_bytes = mem_usage_value * 1024 + elif mem_usage_unit == "MiB": + mem_usage_bytes = mem_usage_value * 1024 ** 2 + elif mem_usage_unit == "GiB": + mem_usage_bytes = mem_usage_value * 1024 ** 3 + + if mem_usage_bytes > threshold_value: + print("[{}]: Memory usage ({} Bytes) is larger than the threshold ({} Bytes)!" + .format(container_name, mem_usage_bytes, threshold_value)) + sys.exit(3) + else: + syslog.syslog(syslog.LOG_ERR, "[memory_checker] Failed to retrieve memory value from '{}'" + .format(mem_usage)) + sys.exit(4) + + +def main(): + parser = argparse.ArgumentParser(description="Check memory usage of a container \ + and an alerting message will be written into syslog if memory usage \ + is larger than the threshold value", usage="/usr/bin/memory_checker ") + parser.add_argument("container_name", help="container name") + # TODO: Currently the threshold value is hard coded as a command line argument and will + # remove this in the new version since we want to read this value from 'CONFIG_DB'. + parser.add_argument("threshold_value", type=int, help="threshold value in bytes") + args = parser.parse_args() + + check_memory_usage(args.container_name, args.threshold_value) + + +if __name__ == "__main__": + main() diff --git a/files/image_config/monit/restart_service b/files/image_config/monit/restart_service new file mode 100755 index 00000000000..40da147e952 --- /dev/null +++ b/files/image_config/monit/restart_service @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 + +""" +restart_service + +This script is part of the feature which will restart the container if memory +usage of it is larger than the threshold value. + +This script is intended to be run by Monit and is used to restart the specified +container if the memory usage of it is larger than the threshold value for X +times within Y cycles/minutes. + +The following is an example in Monit configuration file to show how Monit will run +this script: + +check program container_memory_ with path "/usr/bin/memory_checker " + if status == 3 for X times within Y cycles exec "/usr/bin/restart_service " +""" + +import argparse +import sys +import syslog +import subprocess + + +def get_command_result(command): + """Executes command and return the exit code, stdout and stderr. + + Args: + command: A string contains the command to be executed. + + Returns: + An integer contains the exit code. + A string contains the output of stdout. + A string contains the output of stderr. + """ + command_stdout = "" + command_stderr = "" + + try: + proc_instance = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + shell=True, universal_newlines=True) + command_stdout, command_stderr = proc_instance.communicate() + if proc_instance.returncode != 0: + return 1, command_stdout.strip(), command_stderr.strip() + except (OSError, ValueError) as err: + return 2, command_stdout.strip(), err + + return 0, command_stdout.strip(), command_stderr.strip() + + +def reset_failed_flag(service_name): + """Reset the failed status of a service. + + Args: + service_name: Name of the service. + + Returns: + None + """ + reset_failed_command = "sudo systemctl reset-failed {}.service".format(service_name) + + syslog.syslog(syslog.LOG_INFO, "Resetting failed status of service '{}' ..." + .format(service_name)) + + exit_code, command_stdout, command_stderr = get_command_result(reset_failed_command) + if exit_code == 0: + syslog.syslog(syslog.LOG_INFO, "Succeeded to reset failed status of service '{}.service'." + .format(service_name)) + else: + syslog.syslog(syslog.LOG_ERR, "Failed to reset failed status of service '{}'. Error: {}" + .format(service_name, command_stderr)) + + +def restart_service(service_name): + """Reset the failed status of a service and then restart it. + + Args: + service_name: Name of specified service. + + Returns: + None. + """ + restart_command = "sudo systemctl restart {}.service".format(service_name) + + reset_failed_flag(service_name) + + syslog.syslog(syslog.LOG_INFO, "Restarting service '{}' ...".format(service_name)) + exit_code, command_stdout, command_stderr = get_command_result(restart_command) + if exit_code != 0: + syslog.syslog(syslog.LOG_ERR, "Failed to restart the service '{}'. Error: {}" + .format(service_name, command_stderr)) + + +def main(): + parser = argparse.ArgumentParser(description="Restart a specific service", + usage="/usr/bin/restart_service ") + parser.add_argument("service_name", help="service name") + args = parser.parse_args() + + restart_service(args.service_name) + + +if __name__ == "__main__": + main() From c9c52de9e3c8434be6082b49c7d60500ce0da11e Mon Sep 17 00:00:00 2001 From: Alexander Allen Date: Fri, 28 May 2021 14:22:34 -0400 Subject: [PATCH 4/8] [sonic-platform-common] submodule update (#7727) Includes the following commits 295b68c Add return codes for FW install to component_base constants (#189) e8e617d [sonic_sfp] Handle QSFP DD keys gracefully (#188) --- src/sonic-platform-common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sonic-platform-common b/src/sonic-platform-common index 1213d61136e..295b68ca4c6 160000 --- a/src/sonic-platform-common +++ b/src/sonic-platform-common @@ -1 +1 @@ -Subproject commit 1213d61136e0fd7a193219888ace081b08075620 +Subproject commit 295b68ca4c6ba1b785d5768e36154f1a52479d03 From b486686f907abaf8655b2bfab685e81a38653c75 Mon Sep 17 00:00:00 2001 From: Alexander Allen Date: Fri, 28 May 2021 14:54:19 -0400 Subject: [PATCH 5/8] [sonic-platform-daemons] submodule update (#7709) Includes following commits 9297a29 Mock path early so it will applied to sonic_py_common, mock platform_chassis (#188) 9ba52a2 Mock path early so it will applied to sonic_py_common, mock more swsscommon classes (#187) 1adf47b [chassisd] Add script to initialize chassis info in STATE_DB (#183) --- src/sonic-platform-daemons | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sonic-platform-daemons b/src/sonic-platform-daemons index e60804cd823..9297a294b5e 160000 --- a/src/sonic-platform-daemons +++ b/src/sonic-platform-daemons @@ -1 +1 @@ -Subproject commit e60804cd823090291ad1032e2ef3e9edb11d5a65 +Subproject commit 9297a294b5e6cde0cbfa05d51b185070476dd6c9 From 21b9fccd75618ad0f825b7cf1402fa17529837da Mon Sep 17 00:00:00 2001 From: Alexander Allen Date: Fri, 28 May 2021 15:01:03 -0400 Subject: [PATCH 6/8] [dockers][platform-monitor] Add chassis_db_init to platform monitor tasks (#7596) I added `chassis_db_init` to the startup tasks for the `docker-platform-monitor` docker so that the script is run on startup of the switch and the chassis info is correctly provisioned to STATE_DB. Depends on https://github.com/Azure/sonic-platform-daemons/pull/183 --- .../docker-pmon.supervisord.conf.j2 | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/dockers/docker-platform-monitor/docker-pmon.supervisord.conf.j2 b/dockers/docker-platform-monitor/docker-pmon.supervisord.conf.j2 index 8347ee10f96..90b6d5dfe73 100644 --- a/dockers/docker-platform-monitor/docker-pmon.supervisord.conf.j2 +++ b/dockers/docker-platform-monitor/docker-pmon.supervisord.conf.j2 @@ -41,6 +41,17 @@ dependent_startup=true dependent_startup_wait_for=rsyslogd:running {% endif %} +[program:chassis_db_init] +command=/usr/local/bin/chassis_db_init +priority=3 +autostart=false +autorestart=false +stdout_logfile=syslog +stderr_logfile=syslog +startsecs=0 +dependent_startup=true +dependent_startup_wait_for=rsyslogd:running + {% if not skip_sensors and HAVE_SENSORS_CONF == 1 %} [program:lm-sensors] command=/usr/bin/lm-sensors.sh From 4ae6d3f5c97f9647ab7440b540bfa1c007b15771 Mon Sep 17 00:00:00 2001 From: Wirut Getbamrung Date: Sat, 29 May 2021 02:56:09 +0700 Subject: [PATCH 7/8] [device/celestica]: Fix remaining failed test cases of Seastone-DX010 platform API (#7743) **- Why I did it** - To fix failed test cases of Seastone-DX010 platform APIs that found on [platform_tests](https://github.com/Azure/sonic-mgmt/tree/master/tests/platform_tests/api) script **- How I did it** 1. Add device/celestica/x86_64-cel_seastone-r0/platform.json 2. Update functions to support python3.7 3. Add more functions follow latest sonic_platform_base 4. Fix the bug --- .../platform_components.json | 13 ++ .../sonic_platform/component.py | 37 ++++ .../sonic_platform/psu.py | 160 ++++++++++-------- .../sonic_platform/thermal.py | 68 +++++++- 4 files changed, 203 insertions(+), 75 deletions(-) create mode 100644 device/celestica/x86_64-cel_seastone-r0/platform_components.json diff --git a/device/celestica/x86_64-cel_seastone-r0/platform_components.json b/device/celestica/x86_64-cel_seastone-r0/platform_components.json new file mode 100644 index 00000000000..cd89d358d46 --- /dev/null +++ b/device/celestica/x86_64-cel_seastone-r0/platform_components.json @@ -0,0 +1,13 @@ +{ + "chassis": { + "Celestica-DX010-C32": { + "component": { + "CPLD1": {}, + "CPLD2": {}, + "CPLD3": {}, + "CPLD4": {}, + "BIOS": {} + } + } + } +} \ No newline at end of file diff --git a/device/celestica/x86_64-cel_seastone-r0/sonic_platform/component.py b/device/celestica/x86_64-cel_seastone-r0/sonic_platform/component.py index 2db8418e18a..782708025ab 100644 --- a/device/celestica/x86_64-cel_seastone-r0/sonic_platform/component.py +++ b/device/celestica/x86_64-cel_seastone-r0/sonic_platform/component.py @@ -106,6 +106,29 @@ def get_firmware_version(self): return fw_version + def get_available_firmware_version(self, image_path): + """ + Retrieves the available firmware version of the component + Note: the firmware version will be read from image + Args: + image_path: A string, path to firmware image + Returns: + A string containing the available firmware version of the component + """ + return "N/A" + + def get_firmware_update_notification(self, image_path): + """ + Retrieves a notification on what should be done in order to complete + the component firmware update + Args: + image_path: A string, path to firmware image + Returns: + A string containing the component firmware update notification if required. + By default 'None' value will be used, which indicates that no actions are required + """ + return "None" + def install_firmware(self, image_path): """ Install firmware to module @@ -130,6 +153,20 @@ def install_firmware(self, image_path): return self.__run_command(install_command) + def update_firmware(self, image_path): + """ + Updates firmware of the component + This API performs firmware update: it assumes firmware installation and loading in a single call. + In case platform component requires some extra steps (apart from calling Low Level Utility) + to load the installed firmware (e.g, reboot, power cycle, etc.) - this will be done automatically by API + Args: + image_path: A string, path to firmware image + Raises: + RuntimeError: update failed + """ + return False + + ############################################################## ###################### Device methods ######################## ############################################################## diff --git a/device/celestica/x86_64-cel_seastone-r0/sonic_platform/psu.py b/device/celestica/x86_64-cel_seastone-r0/sonic_platform/psu.py index 8bc95fa4f08..da365dff59a 100644 --- a/device/celestica/x86_64-cel_seastone-r0/sonic_platform/psu.py +++ b/device/celestica/x86_64-cel_seastone-r0/sonic_platform/psu.py @@ -226,72 +226,6 @@ def get_status_led(self): return status_str - def get_name(self): - """ - Retrieves the name of the device - Returns: - string: The name of the device - """ - return PSU_NAME_LIST[self.index] - - def get_presence(self): - """ - Retrieves the presence of the PSU - Returns: - bool: True if PSU is present, False if not - """ - raw = self.__get_gpio_value(self.dx010_psu_gpio[self.index + 1]['prs']) - return int(raw, 10) == 0 - - def get_status(self): - """ - Retrieves the operational status of the device - Returns: - A boolean value, True if device is operating properly, False if not - """ - raw = self.__get_gpio_value( - self.dx010_psu_gpio[self.index + 1]['status']) - return int(raw, 10) == 1 - - def get_model(self): - """ - Retrieves the model number (or part number) of the device - Returns: - string: Model/part number of device - """ - model = self.read_fru(self.eeprom_addr, TLV_ATTR_TYPE_MODEL) - if not model: - return "N/A" - return model - - def get_serial(self): - """ - Retrieves the serial number of the device - Returns: - string: Serial number of device - """ - serial = self.read_fru(self.eeprom_addr, TLV_ATTR_TYPE_SERIAL) - if not serial: - return "N/A" - return serial - - def get_position_in_parent(self): - """ - Retrieves 1-based relative physical position in parent device. If the agent cannot determine the parent-relative position - for some reason, or if the associated value of entPhysicalContainedIn is '0', then the value '-1' is returned - Returns: - integer: The 1-based relative physical position in parent device or -1 if cannot determine the position - """ - return -1 - - def is_replaceable(self): - """ - Indicate whether this device is replaceable. - Returns: - bool: True if it is replaceable. - """ - return True - def get_temperature(self): """ Retrieves current temperature reading from PSU @@ -389,3 +323,97 @@ def get_voltage_low_threshold(self): psu_voltage = float(vout_val) / 1000 return psu_voltage + + def get_maximum_supplied_power(self): + """ + Retrieves the maximum supplied power by PSU + Returns: + A float number, the maximum power output in Watts. + e.g. 1200.1 + """ + psu_power = 0.0 + current_name = "power{}_max" + current_label = "pout1" + + pw_label_path = self.__search_file_by_contain( + self.hwmon_path, current_label, "power") + if pw_label_path: + dir_name = os.path.dirname(pw_label_path) + basename = os.path.basename(pw_label_path) + pw_num = ''.join(list(filter(str.isdigit, basename))) + pw_path = os.path.join( + dir_name, current_name.format(pw_num)) + pw_val = self._api_helper.read_txt_file(pw_path) + psu_power = float(pw_val) / 1000000 + + return psu_power + + ############################################################## + ###################### Device methods ######################## + ############################################################## + + def get_name(self): + """ + Retrieves the name of the device + Returns: + string: The name of the device + """ + return PSU_NAME_LIST[self.index] + + def get_presence(self): + """ + Retrieves the presence of the PSU + Returns: + bool: True if PSU is present, False if not + """ + raw = self.__get_gpio_value(self.dx010_psu_gpio[self.index + 1]['prs']) + return int(raw, 10) == 0 + + def get_status(self): + """ + Retrieves the operational status of the device + Returns: + A boolean value, True if device is operating properly, False if not + """ + raw = self.__get_gpio_value( + self.dx010_psu_gpio[self.index + 1]['status']) + return int(raw, 10) == 1 + + def get_model(self): + """ + Retrieves the model number (or part number) of the device + Returns: + string: Model/part number of device + """ + model = self.read_fru(self.eeprom_addr, TLV_ATTR_TYPE_MODEL) + if not model: + return "N/A" + return model + + def get_serial(self): + """ + Retrieves the serial number of the device + Returns: + string: Serial number of device + """ + serial = self.read_fru(self.eeprom_addr, TLV_ATTR_TYPE_SERIAL) + if not serial: + return "N/A" + return serial + + def get_position_in_parent(self): + """ + Retrieves 1-based relative physical position in parent device. If the agent cannot determine the parent-relative position + for some reason, or if the associated value of entPhysicalContainedIn is '0', then the value '-1' is returned + Returns: + integer: The 1-based relative physical position in parent device or -1 if cannot determine the position + """ + return -1 + + def is_replaceable(self): + """ + Indicate whether this device is replaceable. + Returns: + bool: True if it is replaceable. + """ + return True diff --git a/device/celestica/x86_64-cel_seastone-r0/sonic_platform/thermal.py b/device/celestica/x86_64-cel_seastone-r0/sonic_platform/thermal.py index ce2a45f0c5b..c605a28d220 100644 --- a/device/celestica/x86_64-cel_seastone-r0/sonic_platform/thermal.py +++ b/device/celestica/x86_64-cel_seastone-r0/sonic_platform/thermal.py @@ -23,7 +23,7 @@ "B2F_max_crit": 75, "postion": "asic", "name": "Front-panel temp sensor 1", - "i2c_path": "i2c-5/5-0048/hwmon/hwmon1", # u4 system-inlet + "i2c_path": "i2c-5/5-0048/hwmon", # u4 system-inlet }, 1: { "F2B_max": 50, @@ -32,7 +32,7 @@ "B2F_max_crit": 75, "postion": "asic", "name": "Front-panel temp sensor 2", - "i2c_path": "i2c-6/6-0049/hwmon/hwmon2", # u2 system-inlet + "i2c_path": "i2c-6/6-0049/hwmon", # u2 system-inlet }, 2: { "F2B_max": 70, @@ -41,7 +41,7 @@ "B2F_max_crit": 65, "postion": "asic", "name": "ASIC temp sensor", - "i2c_path": "i2c-7/7-004a/hwmon/hwmon3", # u44 bmc56960-on-board + "i2c_path": "i2c-7/7-004a/hwmon", # u44 bmc56960-on-board }, 3: { "F2B_max": 70, @@ -50,7 +50,7 @@ "B2F_max_crit": 75, "postion": "cpu", "name": "Rear-panel temp sensor 1", - "i2c_path": "i2c-14/14-0048/hwmon/hwmon4", # u9200 cpu-on-board + "i2c_path": "i2c-14/14-0048/hwmon", # u9200 cpu-on-board }, 4: { "F2B_max": 70, @@ -59,7 +59,7 @@ "B2F_max_crit": 75, "postion": "cpu", "name": "Rear-panel temp sensor 2", - "i2c_path": "i2c-15/15-004e/hwmon/hwmon5" # u9201 system-outlet + "i2c_path": "i2c-15/15-004e/hwmon" # u9201 system-outlet } } NULL_VAL = "N/A" @@ -77,17 +77,38 @@ def __init__(self, thermal_index, airflow): self._api_helper = APIHelper() self._airflow = airflow self._thermal_info = THERMAL_INFO[self.index] - self._hwmon_path = "{}/{}".format(I2C_ADAPTER_PATH, self._thermal_info["i2c_path"]) + + self._i2c_hwmon_path = "{}/{}".format( + I2C_ADAPTER_PATH, self._thermal_info["i2c_path"]) + self._hwmon_path = os.path.join( + self._i2c_hwmon_path, self.__get_hwmon_name(self._i2c_hwmon_path)) self.name = self.get_name() self.postion = self._thermal_info["postion"] self.ss_index = 1 + self.minimum_thermal = self.get_temperature() + self.maximum_thermal = self.get_temperature() + + def __get_hwmon_name(self, path): + return os.listdir(path)[0] + def __get_temp(self, temp_file): temp_file_path = os.path.join(self._hwmon_path, temp_file) raw_temp = self._api_helper.read_txt_file(temp_file_path) temp = float(raw_temp)/1000 return float("{:.3f}".format(temp)) + def __get_threshold(self, file_name): + temp_file_path = os.path.join(self._hwmon_path, file_name) + data = self._api_helper.read_txt_file(temp_file_path) + if data: + try: + threshold = float(data) + return round(threshold/1000, 3) + except Exception: + pass + return None + def __set_threshold(self, file_name, temperature): temp_file_path = os.path.join(self._hwmon_path, file_name) try: @@ -114,9 +135,10 @@ def get_high_threshold(self): A float number, the high threshold temperature of thermal in Celsius up to nearest thousandth of one degree Celsius, e.g. 30.125 """ - temp_file = "temp{}_max".format(self.ss_index) - temp = float(self.__get_temp(temp_file)) - return temp + max_crit_key = '{}_max'.format(self._airflow) + high_threshold_file = "temp{}_max".format(self.ss_index) + return self.__get_threshold(high_threshold_file) or self._thermal_info.get(max_crit_key, None) + def get_low_threshold(self): """ @@ -194,6 +216,34 @@ def get_low_critical_threshold(self): """ return 0.001 + def get_minimum_recorded(self): + """ + Retrieves the minimum recorded temperature of thermal + Returns: + A float number, the minimum recorded temperature of thermal in Celsius + up to nearest thousandth of one degree Celsius, e.g. 30.125 + """ + tmp = self.get_temperature() + if tmp < self.minimum_thermal: + self.minimum_thermal = tmp + return self.minimum_thermal + + def get_maximum_recorded(self): + """ + Retrieves the maximum recorded temperature of thermal + Returns: + A float number, the maximum recorded temperature of thermal in Celsius + up to nearest thousandth of one degree Celsius, e.g. 30.125 + """ + tmp = self.get_temperature() + if tmp > self.maximum_thermal: + self.maximum_thermal = tmp + return self.maximum_thermal + + ############################################################## + ###################### Device methods ######################## + ############################################################## + def get_name(self): """ Retrieves the name of the thermal device From 4f8dec9a6f7a97c1c41fc5544eb5b137b57dfaaf Mon Sep 17 00:00:00 2001 From: anamehra <54692434+anamehra@users.noreply.github.com> Date: Fri, 28 May 2021 13:20:11 -0700 Subject: [PATCH 8/8] [multi-asic] Fixed systemd-sonic-generator for multi-asic (#7633) * Fixed systemd-sonic-generator for multi-asic 1. In function insert_instance_number instance_string was malloced for 2 char size which was limiting the instance number value in instance_name to 1 digit. Fixed insert_instance_number to use asprintf to generate instancd_name for any number of instances. Added _GNU_SOURCE to CFLAGS for asprintf. 2. Fixed get_unit_files() to use calloc instead of malloc. Uninitialized memory was causing incorrect string mismatch error while comparing unit file name string. 3. Increased MAX_NUM_TARGETS and MAX_NUM_INSTALL_LINES values to 48 to handle more asic instances. 4. Added build UT support for systemd-sonic-generator: a. Refactor systemd-sonic-generator.c to be used with UT infra. b. Added UT infra to run build UT for systemd-sonic-generator c. Added functional level and program level UT class and test cases. * Resolved review comments. 1. Explicitly setting global pointers to NULL in definitions. 2. Added a space before ": public" in class definitions to align style with SONiC C++ files. * Merged strtok_r statements in single command. Signed-off-by: Anand Mehra --- src/systemd-sonic-generator/.gitignore | 1 + src/systemd-sonic-generator/Makefile | 22 +- src/systemd-sonic-generator/debian/rules | 1 - src/systemd-sonic-generator/ssg-test.cc | 550 ++++++++++++++++++ .../systemd-sonic-generator.c | 133 +++-- .../systemd-sonic-generator.h | 35 ++ .../tests/testfiles/multi_inst_a.service | 9 + .../tests/testfiles/multi_inst_a@.service | 13 + .../tests/testfiles/multi_inst_b.service | 13 + .../tests/testfiles/multi_inst_b@.service | 12 + .../tests/testfiles/single_inst.service | 11 + .../tests/testfiles/test.service | 10 + .../tests/testfiles/test.timer | 10 + 13 files changed, 761 insertions(+), 59 deletions(-) create mode 100644 src/systemd-sonic-generator/ssg-test.cc create mode 100644 src/systemd-sonic-generator/systemd-sonic-generator.h create mode 100644 src/systemd-sonic-generator/tests/testfiles/multi_inst_a.service create mode 100644 src/systemd-sonic-generator/tests/testfiles/multi_inst_a@.service create mode 100644 src/systemd-sonic-generator/tests/testfiles/multi_inst_b.service create mode 100644 src/systemd-sonic-generator/tests/testfiles/multi_inst_b@.service create mode 100644 src/systemd-sonic-generator/tests/testfiles/single_inst.service create mode 100644 src/systemd-sonic-generator/tests/testfiles/test.service create mode 100644 src/systemd-sonic-generator/tests/testfiles/test.timer diff --git a/src/systemd-sonic-generator/.gitignore b/src/systemd-sonic-generator/.gitignore index 06e13f381b6..e81dafbbcbc 100644 --- a/src/systemd-sonic-generator/.gitignore +++ b/src/systemd-sonic-generator/.gitignore @@ -1,4 +1,5 @@ systemd-sonic-generator +ssg_test debian/* !debian/changelog !debian/compat diff --git a/src/systemd-sonic-generator/Makefile b/src/systemd-sonic-generator/Makefile index 0f911ed8a4f..87c6d00e1b4 100644 --- a/src/systemd-sonic-generator/Makefile +++ b/src/systemd-sonic-generator/Makefile @@ -1,5 +1,10 @@ CC=gcc -CFLAGS=-std=gnu99 +CFLAGS=-std=gnu99 -D_GNU_SOURCE + +CPP=g++ +CPPFLAGS=-std=c++11 -D_GNU_SOURCE +LFLAGS=-lpthread -lboost_filesystem -lboost_system +GTEST=/usr/lib/x86_64-linux-gnu/libgtest.a BINARY = systemd-sonic-generator MAIN_TARGET = $(BINARY)_1.0.0_$(CONFIGURED_ARCH).deb @@ -20,3 +25,18 @@ install: $(BINARY) mkdir -p $(DESTDIR)/lib/systemd mkdir -p $(DESTDIR)/lib/systemd/system-generators cp ./systemd-sonic-generator $(DESTDIR)/lib/systemd/system-generators + +.PHONY: test +test: ssg_test + ./ssg_test + +ssg_test: ssg-test.cc systemd-sonic-generator.o + $(CPP) $(CPPFLAGS) -o $@ $^ $(GTEST) $(LFLAGS) + +systemd-sonic-generator.o: systemd-sonic-generator.c + $(CC) $(CFLAGS) -D_SSG_UNITTEST -o $@ -c $^ + +clean: + rm -f ./systemd-sonic-generator + rm -f ./systemd-sonic-generator.o + rm -f ./ssg_test diff --git a/src/systemd-sonic-generator/debian/rules b/src/systemd-sonic-generator/debian/rules index 945fcf9d99b..a482e044c80 100755 --- a/src/systemd-sonic-generator/debian/rules +++ b/src/systemd-sonic-generator/debian/rules @@ -8,7 +8,6 @@ PACKAGEVERSION = $(VERSION) dh $@ override_dh_auto_clean: -override_dh_auto_test: override_dh_auto_build: override_dh_auto_install: make systemd-sonic-generator diff --git a/src/systemd-sonic-generator/ssg-test.cc b/src/systemd-sonic-generator/ssg-test.cc new file mode 100644 index 00000000000..1d0f33d54be --- /dev/null +++ b/src/systemd-sonic-generator/ssg-test.cc @@ -0,0 +1,550 @@ +/*------------------------------------------------------------------ + * ssg-test.cc - systemd-sonic-generator Unit Test + * + * Initial: Apr 2021 + * + * Copyright (c) 2021 by Cisco Systems, Inc. + *------------------------------------------------------------------ + */ +#include +#include +#include +#include +#include +#include +#include "systemd-sonic-generator.h" + +namespace fs = boost::filesystem; + +namespace SSGTest { +#define IS_MULTI_ASIC(x) ((x) > 1) +#define IS_SINGLE_ASIC(x) ((x) <= 1) +#define NUM_UNIT_FILES 6 + +/* + * This test class uses following directory hierarchy for input and output + * data for systemd-sonic-generator. + * + * tests/ssg-test/ --- Test data directory + * | + * |---generated_services.conf + * |---machine.conf (systemd-sonic-generator fetch platform from here) + * |---systemd/ + * | |--- *.service (Test unit files are copied from + * | tests/testfiles/ to here) + * |----test_platform/ (test platform) + * | |---asic.conf + * | + * |----generator/ (Output Directory) + * + */ +const std::string TEST_ROOT_DIR = "tests/ssg-test/"; +const std::string TEST_UNIT_FILE_PREFIX = TEST_ROOT_DIR + "systemd/"; +const std::string TEST_ASIC_CONF_FORMAT = TEST_ROOT_DIR + "%s/asic.conf"; +const std::string TEST_MACHINE_CONF = TEST_ROOT_DIR + "machine.conf"; + +const std::string TEST_PLATFORM_DIR = TEST_ROOT_DIR + "test_platform/"; +const std::string TEST_ASIC_CONF = TEST_PLATFORM_DIR + "asic.conf"; + +const std::string TEST_OUTPUT_DIR = TEST_ROOT_DIR + "generator/"; + +const std::string TEST_CONFIG_FILE = TEST_ROOT_DIR + "generated_services.conf"; + +const std::string TEST_UNIT_FILES = "tests/testfiles/"; + +/* Input data for generated_services.conf */ +const std::vector generated_services = { + "multi_inst_a.service", /* Single instance of a multi asic service a */ + "multi_inst_a@.service", /* Multi-instance of a multi asic service a */ + "multi_inst_b@.service", /* Multi-instance of a multi asic service b */ + "single_inst.service", /* A single instance service */ + "test.service", /* A single instance test service + to test dependency creation */ + "test.timer", /* A timer service */ +}; + +static std::mutex g_ssg_test_mutex; + +class SystemdSonicGeneratorFixture : public testing::Test { + protected: + /* Save global variables before running tests */ + virtual void SetUp() { + /* one test runs at a time */ + g_ssg_test_mutex.lock(); + + unit_file_prefix_ = g_unit_file_prefix; + config_file_ = g_config_file; + machine_config_file_ = g_machine_config_file; + asic_conf_format_ = g_asic_conf_format; + } + + /* Restore global vars */ + virtual void TearDown() { + g_unit_file_prefix = unit_file_prefix_; + g_config_file = config_file_; + g_machine_config_file = machine_config_file_; + g_asic_conf_format = asic_conf_format_; + + g_ssg_test_mutex.unlock(); + } + + private: + const char* unit_file_prefix_; + const char* config_file_; + const char* machine_config_file_; + const char* asic_conf_format_; +}; + +/* + * class SsgFunctionTest + * Implements functions to execute functional level tests. + */ +class SsgFunctionTest : public SystemdSonicGeneratorFixture { + protected: + /* This function generates the generated_services.conf file */ + void generate_generated_services_conf() { + FILE* fp = fopen(TEST_CONFIG_FILE.c_str(), "w"); + ASSERT_NE(fp, nullptr); + for (std::string str : generated_services) { + fputs(str.c_str(), fp); + fputs("\n", fp); + } + fclose(fp); + } + + /* copy files from src_dir to dest_dir */ + void copyfiles(const char* src_dir, const char* dest_dir) { + // Iterate through the source directory + for (fs::directory_iterator file(src_dir); + file != fs::directory_iterator(); ++file) { + try { + fs::path current(file->path()); + if(!fs::is_directory(current)) { + /* Copy file */ + fs::copy_file( current, dest_dir / current.filename()); + } + } + catch(fs::filesystem_error const & e) { + std:: cerr << e.what() << '\n'; + } + } + } + + /* Save global variables before running tests */ + virtual void SetUp() { + FILE* fp; + SystemdSonicGeneratorFixture::SetUp(); + + /* Setup Input and Output directories and files */ + fs::path path{TEST_UNIT_FILE_PREFIX.c_str()}; + fs::create_directories(path); + path = fs::path(TEST_OUTPUT_DIR.c_str()); + fs::create_directories(path); + path = fs::path(TEST_PLATFORM_DIR.c_str()); + fs::create_directories(path); + fp = fopen(TEST_MACHINE_CONF.c_str(), "w"); + ASSERT_NE(fp, nullptr); + fputs("onie_platform=test_platform", fp); + fclose(fp); + generate_generated_services_conf(); + copyfiles(TEST_UNIT_FILES.c_str(), TEST_UNIT_FILE_PREFIX.c_str()); + } + + /* Restore global vars */ + virtual void TearDown() { + /* Delete ssg_test directory */ + EXPECT_TRUE(fs::exists(TEST_ROOT_DIR.c_str())); + fs::path path{TEST_ROOT_DIR.c_str()}; + fs::remove_all(path); + + SystemdSonicGeneratorFixture::TearDown(); + } + + private: +}; + +/* + * class SsgMainTest + * Implements functions to test ssg_main routine. + */ +class SsgMainTest : public SsgFunctionTest { + protected: + /* Retrun true if string belongs to a multi instance service */ + bool is_multi_instance(const std::string str) { + return (str.find("@") != std::string::npos) ? true : false; + } + + /* Returns true if it is a timer service */ + bool is_timer_service(const std::string str) { + return (str.find(".timer") != std::string::npos) ? true : false; + } + + /* Find a string in a file */ + bool find_string_in_file(std::string str, + std::string file_name, + int num_asics) { + bool found = false; + std::string line; + + std::ifstream file(TEST_UNIT_FILE_PREFIX + file_name); + while (getline(file, line) && !found) { + if (str == line) { + found = true; + } + } + return found; + } + + /* This function validates if a given dependency list for an unit file + * exists in the unit file as per expected_result. The items in the list + * should exist if expected_result is true. + */ + void validate_output_dependency_list(std::vector strs, + std::string target, + bool expected_result, + int num_asics) { + for (std::string str : strs) { + bool finished = false; + for (int i = 0 ; i < num_asics && !finished; ++i) { + auto str_t = str; + if (is_multi_instance(str)) { + /* insert instance id in string */ + str_t = (boost::format{str} % i).str(); + } else { + /* Run once for single instance */ + finished = true; + } + EXPECT_EQ(find_string_in_file(str_t, target, num_asics), + expected_result) + << "Error validating " + str_t + " in " + target; + } + } + } + + /* This function validates if unit file paths in the provided + * list strs exists or not as per expected_result. The unit files + * should exist if expected_result is true. + */ + void validate_output_unit_files(std::vector strs, + std::string target, + bool expected_result, + int num_asics) { + for (std::string str : strs) { + bool finished = false; + for (int i = 0 ; i < num_asics && !finished; ++i) { + auto str_t = str; + if (is_multi_instance(str)) { + /* insert instance id in string */ + str_t = (boost::format{str} % i).str(); + } else { + /* Run once for single instance */ + finished = true; + } + fs::path path{TEST_OUTPUT_DIR + target + "/" + str_t}; + EXPECT_EQ(fs::exists(path), expected_result) + << "Failed validation: " << path; + } + } + } + + /* + * This function validates the generated dependencies in a Unit File. + */ + void validate_depedency_in_unit_file(int num_asics) { + std::string test_service = "test.service"; + + /* Validate Unit file dependency creation for multi instance + * services. These entries should be present for multi asic + * system but not present for single asic system. + */ + validate_output_dependency_list(multi_asic_dependency_list, + test_service, IS_MULTI_ASIC(num_asics), num_asics); + + /* Validate Unit file dependency creation for single instance + * services. These entries should not be present for multi asic + * system but present for single asic system. + */ + validate_output_dependency_list(single_asic_dependency_list, + test_service, IS_SINGLE_ASIC(num_asics), num_asics); + + /* Validate Unit file dependency creation for single instance + * common services. These entries should not be present for multi + * and single asic system. + */ + validate_output_dependency_list(common_dependency_list, + test_service, true, num_asics); + } + + /* + * This function validates the list of generated Service Unit Files. + */ + void validate_service_file_generated_list(int num_asics) { + std::string test_target = "multi-user.target.wants"; + validate_output_unit_files(multi_asic_service_list, + test_target, IS_MULTI_ASIC(num_asics), num_asics); + validate_output_unit_files(single_asic_service_list, + test_target, IS_SINGLE_ASIC(num_asics), num_asics); + validate_output_unit_files(common_service_list, + test_target, true, num_asics); + } + + /* ssg_main test routine. + * input: num_asics number of asics + */ + void ssg_main_test(int num_asics) { + FILE* fp; + std::vector argv_; + std::vector arguments = { + "ssg_main", + TEST_OUTPUT_DIR.c_str() + }; + std::string num_asic_str = "NUM_ASIC=" + std::to_string(num_asics); + + std::string unit_file_path = fs::current_path().string() + "/" +TEST_UNIT_FILE_PREFIX; + g_unit_file_prefix = unit_file_path.c_str(); + g_config_file = TEST_CONFIG_FILE.c_str(); + g_machine_config_file = TEST_MACHINE_CONF.c_str(); + g_asic_conf_format = TEST_ASIC_CONF_FORMAT.c_str(); + + /* Set NUM_ASIC value in asic.conf */ + fp = fopen(TEST_ASIC_CONF.c_str(), "w"); + ASSERT_NE(fp, nullptr); + fputs(num_asic_str.c_str(), fp); + fclose(fp); + + /* Create argv list for ssg_main. */ + for (const auto& arg : arguments) { + argv_.push_back((char*)arg.data()); + } + argv_.push_back(nullptr); + + /* Call ssg_main */ + EXPECT_EQ(ssg_main(argv_.size(), argv_.data()), 0); + + /* Validate systemd service template creation. */ + validate_service_file_generated_list(num_asics); + + /* Validate Test Unit file for dependency creation. */ + validate_depedency_in_unit_file(num_asics); + } + + /* Save global variables before running tests */ + virtual void SetUp() { + SsgFunctionTest::SetUp(); + } + + /* Restore global vars */ + virtual void TearDown() { + SsgFunctionTest::TearDown(); + } + + + private: + static const std::vector single_asic_service_list; + static const std::vector multi_asic_service_list; + static const std::vector common_service_list; + static const std::vector single_asic_dependency_list; + static const std::vector multi_asic_dependency_list; + static const std::vector common_dependency_list; +}; + +/* + * The following list defines the Service unit files symlinks generated by + * Systemd sonic generator for single and multi asic systems. The test case + * use these lists to check for presence/absence of unit files based on + * num_asics value. + */ + +/* Systemd service Unit file list for single asic only system */ +const std::vector +SsgMainTest::single_asic_service_list = { + "multi_inst_b.service", +}; + +/* Systemd service Unit file list for multi asic only system. + * %1% is formatter for boost::format API and replaced by asic num. + */ +const std::vector +SsgMainTest::multi_asic_service_list = { + "multi_inst_a@%1%.service", + "multi_inst_b@%1%.service", +}; + +/* Common Systemd service Unit file list for single and multi asic system. */ +const std::vector +SsgMainTest::common_service_list = { + "multi_inst_a.service", + "single_inst.service", + "test.service", + +}; + +/* + * The following list defines the systemd dependencies in a unit file to be + * varified for single and multi asic systems. Based on num_asics and type of + * service listed as dependency in Unit file, systemd sonic generator modifies + * the original unit file, if required, for multi asic system. + * For example: if test.service file defines a dependency "After=multi_inst_a.service", + * as multi_inst_a.service is a multi instance service, + * for a system with 2 asics, systemd sonic generator shall modify + * test.service to include following dependency strings: + * "After=multi_inst_a@0.service" + * After=multi_inst_a@1.service" + */ + +/* Systemd service Unit file dependency entries for Single asic system. */ +const std::vector +SsgMainTest::single_asic_dependency_list = { + "After=multi_inst_a.service multi_inst_b.service", +}; + +/* Systemd service Unit file dependency entries for multi asic system. */ +const std::vector +SsgMainTest::multi_asic_dependency_list = { + "After=multi_inst_a@%1%.service", + "After=multi_inst_b@%1%.service", +}; + +/* Common Systemd service Unit file dependency entries for single and multi asic + * systems. + */ +const std::vector +SsgMainTest::common_dependency_list = { + "Before=single_inst.service", +}; + +/* Test get functions for global vasr*/ +TEST_F(SystemdSonicGeneratorFixture, get_global_vars) { + EXPECT_EQ(g_unit_file_prefix, nullptr); + EXPECT_STREQ(get_unit_file_prefix(), UNIT_FILE_PREFIX); + g_unit_file_prefix = TEST_UNIT_FILE_PREFIX.c_str(); + EXPECT_STREQ(get_unit_file_prefix(), TEST_UNIT_FILE_PREFIX.c_str()); + + EXPECT_EQ(g_config_file, nullptr); + EXPECT_STREQ(get_config_file(), CONFIG_FILE); + g_config_file = TEST_CONFIG_FILE.c_str(); + EXPECT_STREQ(get_config_file(), TEST_CONFIG_FILE.c_str()); + + EXPECT_EQ(g_machine_config_file, nullptr); + EXPECT_STREQ(get_machine_config_file(), MACHINE_CONF_FILE); + g_machine_config_file = TEST_MACHINE_CONF.c_str(); + EXPECT_STREQ(get_machine_config_file(), TEST_MACHINE_CONF.c_str()); + + EXPECT_EQ(g_asic_conf_format, nullptr); + EXPECT_STREQ(get_asic_conf_format(), ASIC_CONF_FORMAT); + g_asic_conf_format = TEST_ASIC_CONF_FORMAT.c_str(); + EXPECT_STREQ(get_asic_conf_format(), TEST_ASIC_CONF_FORMAT.c_str()); +} + +TEST_F(SystemdSonicGeneratorFixture, global_vars) { + EXPECT_EQ(g_unit_file_prefix, nullptr); + EXPECT_STREQ(get_unit_file_prefix(), UNIT_FILE_PREFIX); + + EXPECT_EQ(g_config_file, nullptr); + EXPECT_STREQ(get_config_file(), CONFIG_FILE); + + EXPECT_EQ(g_machine_config_file, nullptr); + EXPECT_STREQ(get_machine_config_file(), MACHINE_CONF_FILE); +} + +/* TEST machine/unit/config if file is missing */ +TEST_F(SsgFunctionTest, missing_file) { + EXPECT_TRUE(fs::exists(TEST_MACHINE_CONF.c_str())); + EXPECT_TRUE(fs::exists(TEST_UNIT_FILE_PREFIX.c_str())); + EXPECT_TRUE(fs::exists(TEST_OUTPUT_DIR.c_str())); + EXPECT_TRUE(fs::exists(TEST_PLATFORM_DIR.c_str())); +} + +/* TEST insert_instance_number() */ +TEST_F(SsgFunctionTest, insert_instance_number) { + char input[] = "test@.service"; + for (int i = 0; i <= 100; ++i) { + std::string out = "test@" + std::to_string(i) + ".service"; + char* ret = insert_instance_number(input, i); + ASSERT_NE(ret, nullptr); + EXPECT_STREQ(ret, out.c_str()); + } +} + +/* TEST get_num_of_asic() */ +TEST_F(SsgFunctionTest, get_num_of_asic) { + FILE* fp; + + g_machine_config_file = TEST_MACHINE_CONF.c_str(); + g_asic_conf_format = TEST_ASIC_CONF_FORMAT.c_str(); + + fp = fopen(TEST_ASIC_CONF.c_str(), "w"); + ASSERT_NE(fp, nullptr); + fputs("NUM_ASIC=1", fp); + fclose(fp); + EXPECT_EQ(get_num_of_asic(), 1); + + fp = fopen(TEST_ASIC_CONF.c_str(), "w"); + ASSERT_NE(fp, nullptr); + fputs("NUM_ASIC=10", fp); + fclose(fp); + EXPECT_EQ(get_num_of_asic(), 10); + + fp = fopen(TEST_ASIC_CONF.c_str(), "w"); + ASSERT_NE(fp, nullptr); + fputs("NUM_ASIC=40", fp); + fclose(fp); + EXPECT_EQ(get_num_of_asic(), 40); +} + +/* TEST get_unit_files()*/ +TEST_F(SsgFunctionTest, get_unit_files) { + g_unit_file_prefix = TEST_UNIT_FILE_PREFIX.c_str(); + g_config_file = TEST_CONFIG_FILE.c_str(); + char* unit_files[NUM_UNIT_FILES]; + int num_unit_files = get_unit_files(unit_files); + EXPECT_EQ(num_unit_files, NUM_UNIT_FILES); + for (std::string service : generated_services) { + bool found = false; + for (auto& unit_file : unit_files) { + if(unit_file == service) { + found = true; + break; + } + } + EXPECT_TRUE(found) << "unit file not found: " << service; + } +} + +/* TEST ssg_main() argv error */ +TEST_F(SsgMainTest, ssg_main_argv) { + FILE* fp; + std::vector argv_; + std::vector arguments = { + "ssg_main", + }; + + /* Create argv list for ssg_main. */ + for (const auto& arg : arguments) { + argv_.push_back((char*)arg.data()); + } + + /* Call ssg_main */ + EXPECT_EQ(ssg_main(argv_.size(), argv_.data()), 1); +} + +/* TEST ssg_main() single asic */ +TEST_F(SsgMainTest, ssg_main_single_npu) { + ssg_main_test(1); +} + +/* TEST ssg_main() multi(10) asic */ +TEST_F(SsgMainTest, ssg_main_10_npu) { + ssg_main_test(10); +} + +/* TEST ssg_main() multi(40) asic */ +TEST_F(SsgMainTest, ssg_main_40_npu) { + ssg_main_test(40); +} +} + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/systemd-sonic-generator/systemd-sonic-generator.c b/src/systemd-sonic-generator/systemd-sonic-generator.c index 1052e2db622..96193b9fe3e 100644 --- a/src/systemd-sonic-generator/systemd-sonic-generator.c +++ b/src/systemd-sonic-generator/systemd-sonic-generator.c @@ -9,14 +9,36 @@ #include #include -#define MAX_NUM_TARGETS 15 -#define MAX_NUM_INSTALL_LINES 15 +#define MAX_NUM_TARGETS 48 +#define MAX_NUM_INSTALL_LINES 48 #define MAX_NUM_UNITS 128 #define MAX_BUF_SIZE 512 -static const char* UNIT_FILE_PREFIX = "/usr/lib/systemd/system/"; -static const char* CONFIG_FILE = "/etc/sonic/generated_services.conf"; -static const char* MACHINE_CONF_FILE = "/host/machine.conf"; +const char* UNIT_FILE_PREFIX = "/usr/lib/systemd/system/"; +const char* CONFIG_FILE = "/etc/sonic/generated_services.conf"; +const char* MACHINE_CONF_FILE = "/host/machine.conf"; +const char* ASIC_CONF_FORMAT = "/usr/share/sonic/device/%s/asic.conf"; + +const char* g_unit_file_prefix = NULL; +const char* get_unit_file_prefix() { + return (g_unit_file_prefix) ? g_unit_file_prefix : UNIT_FILE_PREFIX; +} + +const char* g_config_file = NULL; +const char* get_config_file() { + return (g_config_file) ? g_config_file : CONFIG_FILE; +} + +const char* g_machine_config_file = NULL; +const char* get_machine_config_file() { + return (g_machine_config_file) ? g_machine_config_file : MACHINE_CONF_FILE; +} + +const char* g_asic_conf_format = NULL; +const char* get_asic_conf_format() { + return (g_asic_conf_format) ? g_asic_conf_format : ASIC_CONF_FORMAT; +} + static int num_asics; static char** multi_instance_services; static int num_multi_inst; @@ -128,7 +150,7 @@ static int get_install_targets_from_line(char* target_string, char* install_type strcat(final_target, install_type); free(target); - + targets[num_targets + existing_targets] = strdup(final_target); num_targets++; } @@ -152,12 +174,12 @@ static void replace_multi_inst_dep(char *src) { ssize_t nread; bool section_done = false; char tmp_file_path[PATH_MAX]; - + /* Assumes that the service files has 3 sections, * in the order: Unit, Service and Install. - * Assumes that the timer file has 3 sectiosn, + * Assumes that the timer file has 3 sections, * in the order: Unit, Timer and Install. - * Read service dependency from Unit and Install + * Read service dependency from Unit and Install * sections, replace if dependent on multi instance * service. */ @@ -166,7 +188,7 @@ static void replace_multi_inst_dep(char *src) { fp_tmp = fopen(tmp_file_path, "w"); while ((nread = getline(&line, &len, fp_src)) != -1 ) { - if ((strstr(line, "[Service]") != NULL) || + if ((strstr(line, "[Service]") != NULL) || (strstr(line, "[Timer]") != NULL)) { section_done = true; fputs(line,fp_tmp); @@ -188,7 +210,7 @@ static void replace_multi_inst_dep(char *src) { } else { service_name = strdup(word); service_name = strtok_r(service_name, ".", &save_ptr2); - type = strtok_r(NULL, " ", &save_ptr2); + type = strtok_r(NULL, "\n", &save_ptr2); if (is_multi_instance_service(word)) { for(i = 0; i < num_asics; i++) { snprintf(buf, MAX_BUF_SIZE, "%s=%s@%d.%s\n", @@ -215,7 +237,7 @@ static void replace_multi_inst_dep(char *src) { rename(tmp_file_path, src); } -static int get_install_targets(char* unit_file, char* targets[]) { +int get_install_targets(char* unit_file, char* targets[]) { /*** Returns install targets for a unit file @@ -234,7 +256,7 @@ static int get_install_targets(char* unit_file, char* targets[]) { char *instance_name; char *dot_ptr; - strcpy(file_path, UNIT_FILE_PREFIX); + strcpy(file_path, get_unit_file_prefix()); strcat(file_path, unit_file); instance_name = strdup(unit_file); @@ -280,7 +302,7 @@ static int get_install_targets(char* unit_file, char* targets[]) { } -static int get_unit_files(char* unit_files[]) { +int get_unit_files(char* unit_files[]) { /*** Reads a list of unit files to be installed from /etc/sonic/generated_services.conf ***/ @@ -289,18 +311,19 @@ static int get_unit_files(char* unit_files[]) { size_t len = 0; ssize_t read; char *pos; + const char* config_file = get_config_file(); - fp = fopen(CONFIG_FILE, "r"); + fp = fopen(config_file, "r"); if (fp == NULL) { - fprintf(stderr, "Failed to open %s\n", CONFIG_FILE); + fprintf(stderr, "Failed to open %s\n", config_file); exit(EXIT_FAILURE); } int num_unit_files = 0; num_multi_inst = 0; - multi_instance_services = malloc(MAX_NUM_UNITS * sizeof(char *)); + multi_instance_services = calloc(MAX_NUM_UNITS, sizeof(char *)); while ((read = getline(&line, &len, fp)) != -1) { if (num_unit_files >= MAX_NUM_UNITS) { @@ -312,7 +335,7 @@ static int get_unit_files(char* unit_files[]) { /* Get the multi-instance services */ pos = strchr(line, '@'); if (pos != NULL) { - multi_instance_services[num_multi_inst] = malloc(strlen(line)*sizeof(char)); + multi_instance_services[num_multi_inst] = calloc(strlen(line), sizeof(char)); strncpy(multi_instance_services[num_multi_inst], line, pos-line); num_multi_inst++; } @@ -334,41 +357,33 @@ static int get_unit_files(char* unit_files[]) { } -static char* insert_instance_number(char* unit_file, int instance) { +char* insert_instance_number(char* unit_file, int instance) { /*** Adds an instance number to a systemd template name E.g. given unit_file='example@.service', instance=3, - returns a pointer to 'example@1.service' + returns a pointer to 'example@3.service' ***/ - char* prefix; - char* suffix; - char* instance_string; char* instance_name; - char* temp_unit_file; - - instance_string = malloc(2 * sizeof(char)); - snprintf(instance_string, 2, "%d", instance); - - instance_name = malloc(strlen(unit_file) + 2); + int ret; + int prefix_len; + const char *suffix = strchr(unit_file, '@'); + if (!suffix) { + fprintf(stderr, "Invalid unit file %s for instance %d\n", unit_file, instance); + return NULL; + } - if (instance_name == NULL) { + /*** + suffix is "@.service", set suffix=".service" + prefix_len is length of "example@" + ***/ + prefix_len = ++suffix - unit_file; + ret = asprintf(&instance_name, "%.*s%d%s", prefix_len, unit_file, instance, suffix); + if (ret == -1) { fprintf(stderr, "Error creating instance %d of %s\n", instance, unit_file); return NULL; } - temp_unit_file = strdup(unit_file); - prefix = strtok(temp_unit_file, "@"); - suffix = strtok(NULL, "@"); - - strcpy(instance_name, prefix); - strcat(instance_name, "@"); - strcat(instance_name, instance_string); - strcat(instance_name, suffix); - - free(instance_string); - free(temp_unit_file); - return instance_name; } @@ -381,7 +396,7 @@ static int create_symlink(char* unit, char* target, char* install_dir, int insta char* unit_instance; int r; - strcpy(src_path, UNIT_FILE_PREFIX); + strcpy(src_path, get_unit_file_prefix()); strcat(src_path, unit); if (instance < 0) { @@ -462,7 +477,7 @@ static int install_unit_file(char* unit_file, char* target, char* install_dir) { assert(unit_file); assert(target); - + if ((num_asics > 1) && strstr(unit_file, "@") != NULL) { @@ -476,16 +491,16 @@ static int install_unit_file(char* unit_file, char* target, char* install_dir) { } r = create_symlink(unit_file, target_instance, install_dir, i); - if (r < 0) + if (r < 0) fprintf(stderr, "Error installing %s for target %s\n", unit_file, target_instance); - + free(target_instance); - } + } } else { r = create_symlink(unit_file, target, install_dir, -1); - if (r < 0) + if (r < 0) fprintf(stderr, "Error installing %s for target %s\n", unit_file, target); } @@ -493,7 +508,7 @@ static int install_unit_file(char* unit_file, char* target, char* install_dir) { } -static int get_num_of_asic() { +int get_num_of_asic() { /*** Determines if the current platform is single or multi-ASIC ***/ @@ -507,11 +522,12 @@ static int get_num_of_asic() { char asic_file[512]; char* str_num_asic; int num_asic = 1; + const char* machine_config_file = get_machine_config_file(); - fp = fopen(MACHINE_CONF_FILE, "r"); + fp = fopen(machine_config_file, "r"); if (fp == NULL) { - fprintf(stderr, "Failed to open %s\n", MACHINE_CONF_FILE); + fprintf(stderr, "Failed to open %s\n", machine_config_file); exit(EXIT_FAILURE); } @@ -524,10 +540,9 @@ static int get_num_of_asic() { break; } } - fclose(fp); if(platform != NULL) { - snprintf(asic_file, 512, "/usr/share/sonic/device/%s/asic.conf", platform); + snprintf(asic_file, 512, get_asic_conf_format(), platform); fp = fopen(asic_file, "r"); if (fp != NULL) { while ((nread = getline(&line, &len, fp)) != -1) { @@ -549,8 +564,7 @@ static int get_num_of_asic() { } - -int main(int argc, char **argv) { +int ssg_main(int argc, char **argv) { char* unit_files[MAX_NUM_UNITS]; char install_dir[PATH_MAX]; char* targets[MAX_NUM_TARGETS]; @@ -567,10 +581,8 @@ int main(int argc, char **argv) { } num_asics = get_num_of_asic(); - strcpy(install_dir, argv[1]); strcat(install_dir, "/"); - num_unit_files = get_unit_files(unit_files); // For each unit file, get the installation targets and install the unit @@ -610,3 +622,10 @@ int main(int argc, char **argv) { return 0; } + + +#ifndef _SSG_UNITTEST +int main(int argc, char **argv) { + return ssg_main(argc, argv); +} +#endif diff --git a/src/systemd-sonic-generator/systemd-sonic-generator.h b/src/systemd-sonic-generator/systemd-sonic-generator.h new file mode 100644 index 00000000000..25c179caa0b --- /dev/null +++ b/src/systemd-sonic-generator/systemd-sonic-generator.h @@ -0,0 +1,35 @@ +/*------------------------------------------------------------------ + * systemd-sonic-generator.h - Header file + * + * Initial: Apr 2021 + * + * Copyright (c) 2021 by Cisco Systems, Inc. + *------------------------------------------------------------------ + */ +#ifdef __cplusplus +extern "C" { +#endif + +/* expose global vars for testing purpose */ +extern const char* UNIT_FILE_PREFIX; +extern const char* CONFIG_FILE; +extern const char* MACHINE_CONF_FILE; +extern const char* ASIC_CONF_FORMAT; +extern const char* g_unit_file_prefix; +extern const char* g_config_file; +extern const char* g_machine_config_file; +extern const char* g_asic_conf_format; + +/* C-functions under test */ +extern const char* get_unit_file_prefix(); +extern const char* get_config_file(); +extern const char* get_machine_config_file(); +extern const char* get_asic_conf_format(); +extern char* insert_instance_number(char* unit_file, int instance); +extern int ssg_main(int argc, char** argv); +extern int get_num_of_asic(); +extern int get_install_targets(char* unit_file, char* targets[]); +extern int get_unit_files(char* unit_files[]); +#ifdef __cplusplus +} +#endif diff --git a/src/systemd-sonic-generator/tests/testfiles/multi_inst_a.service b/src/systemd-sonic-generator/tests/testfiles/multi_inst_a.service new file mode 100644 index 00000000000..500cb0ae164 --- /dev/null +++ b/src/systemd-sonic-generator/tests/testfiles/multi_inst_a.service @@ -0,0 +1,9 @@ +[Unit] +Description=Multi Instance A Test service +StartLimitIntervalSec=1200 +StartLimitBurst=3 +[Service] +User=root +ExecStop=/usr/bin/test.sh stop +[Install] +WantedBy=multi-user.target diff --git a/src/systemd-sonic-generator/tests/testfiles/multi_inst_a@.service b/src/systemd-sonic-generator/tests/testfiles/multi_inst_a@.service new file mode 100644 index 00000000000..816d698b4d1 --- /dev/null +++ b/src/systemd-sonic-generator/tests/testfiles/multi_inst_a@.service @@ -0,0 +1,13 @@ +[Unit] +Description=Multi INstance A test Service + +After=multi_inst_a.service +StartLimitIntervalSec=1200 +StartLimitBurst=3 +[Service] +User=root +ExecStop=/usr/bin/test.sh stop %i +Restart=always +RestartSec=30 +[Install] +WantedBy=multi-user.target diff --git a/src/systemd-sonic-generator/tests/testfiles/multi_inst_b.service b/src/systemd-sonic-generator/tests/testfiles/multi_inst_b.service new file mode 100644 index 00000000000..04960be66c4 --- /dev/null +++ b/src/systemd-sonic-generator/tests/testfiles/multi_inst_b.service @@ -0,0 +1,13 @@ +[Unit] +Description=Multi instance b Test service + +Requires=multi_inst_a.service +StartLimitIntervalSec=1200 +StartLimitBurst=3 +[Service] +User=root +ExecStop=/usr/bin/test.sh stop +Restart=always +RestartSec=30 +[Install] +WantedBy=multi-user.target diff --git a/src/systemd-sonic-generator/tests/testfiles/multi_inst_b@.service b/src/systemd-sonic-generator/tests/testfiles/multi_inst_b@.service new file mode 100644 index 00000000000..dee76391da9 --- /dev/null +++ b/src/systemd-sonic-generator/tests/testfiles/multi_inst_b@.service @@ -0,0 +1,12 @@ +[Unit] +Description=Multi instance b Test service +Requires=multi_inst_a@%i.service +StartLimitIntervalSec=1200 +StartLimitBurst=3 +[Service] +User=root +ExecStart=/usr/local/bin/test.sh wait %i +Restart=always +RestartSec=30 +[Install] +WantedBy=multi-user.target diff --git a/src/systemd-sonic-generator/tests/testfiles/single_inst.service b/src/systemd-sonic-generator/tests/testfiles/single_inst.service new file mode 100644 index 00000000000..7f0fd725245 --- /dev/null +++ b/src/systemd-sonic-generator/tests/testfiles/single_inst.service @@ -0,0 +1,11 @@ +[Unit] +Description=Platform monitor container +Requires=multi_inst_a.service +StartLimitIntervalSec=1200 +StartLimitBurst=3 +[Service] +ExecStart=/usr/bin/test.sh wait +Restart=always +RestartSec=30 +[Install] +WantedBy=multi-user.target diff --git a/src/systemd-sonic-generator/tests/testfiles/test.service b/src/systemd-sonic-generator/tests/testfiles/test.service new file mode 100644 index 00000000000..e8b37f641fd --- /dev/null +++ b/src/systemd-sonic-generator/tests/testfiles/test.service @@ -0,0 +1,10 @@ +[Unit] +Description=Multi ASIC Test service +After=multi_inst_a.service multi_inst_b.service +Before=single_inst.service +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/bin/test.sh start +[Install] +WantedBy=multi-user.target diff --git a/src/systemd-sonic-generator/tests/testfiles/test.timer b/src/systemd-sonic-generator/tests/testfiles/test.timer new file mode 100644 index 00000000000..254bec362ac --- /dev/null +++ b/src/systemd-sonic-generator/tests/testfiles/test.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Test Timer service +After=multi_inst_b.service +[Timer] +OnUnitActiveSec=0 sec +OnBootSec=3min 30 sec +Unit=snmp.service +[Install] +WantedBy=timers.target +WantedBy=multi_inst_b.service