[device/celestica]: Fix failed test cases of DX010 platform APIs (#6564)

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

Co-authored-by: 119064273 <2276096708@qq.com>
Co-authored-by: Eric Zhu <erzhu@celestica.com>
Co-authored-by: doni@celestica.com <doni@celestica.com>
This commit is contained in:
Wirut Getbamrung 2021-04-03 00:08:31 +07:00 committed by GitHub
parent 27f4daba54
commit fbcb9403e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 2028 additions and 425 deletions

View File

@ -1,4 +1,246 @@
{ {
"chassis": {
"name": "Celestica-DX010-C32",
"components": [
{
"name": "CPLD1"
},
{
"name": "CPLD2"
},
{
"name": "CPLD3"
},
{
"name": "CPLD4"
},
{
"name": "BIOS"
}
],
"fans": [
{
"name": "FAN-1F"
},
{
"name": "FAN-1R"
},
{
"name": "FAN-2F"
},
{
"name": "FAN-2R"
},
{
"name": "FAN-3F"
},
{
"name": "FAN-3R"
},
{
"name": "FAN-4F"
},
{
"name": "FAN-4R"
},
{
"name": "FAN-5F"
},
{
"name": "FAN-5R"
}
],
"fan_drawers": [
{
"name": "Drawer1",
"fans": [
{
"name": "FAN-1F"
},
{
"name": "FAN-1R"
}
]
},
{
"name": "Drawer2",
"fans": [
{
"name": "FAN-2F"
},
{
"name": "FAN-2R"
}
]
},
{
"name": "Drawer3",
"fans": [
{
"name": "FAN-3F"
},
{
"name": "FAN-3R"
}
]
},
{
"name": "Drawer4",
"fans": [
{
"name": "FAN-4F"
},
{
"name": "FAN-4R"
}
]
},
{
"name": "Drawer5",
"fans": [
{
"name": "FAN-5F"
},
{
"name": "FAN-5R"
}
]
}
],
"psus": [
{
"name": "PSU-1",
"fans": [
{
"name": "PSU-1 FAN-1"
}
]
},
{
"name": "PSU-2",
"fans": [
{
"name": "PSU-2 FAN-1"
}
]
}
],
"thermals": [
{
"name": "Front-panel temp sensor 1"
},
{
"name": "Front-panel temp sensor 2"
},
{
"name": "ASIC temp sensor"
},
{
"name": "Rear-panel temp sensor 1"
},
{
"name": "Rear-panel temp sensor 2"
}
],
"sfps": [
{
"name": "Ethernet124"
},
{
"name": "Ethernet0"
},
{
"name": "Ethernet4"
},
{
"name": "Ethernet8"
},
{
"name": "Ethernet12"
},
{
"name": "Ethernet16"
},
{
"name": "Ethernet20"
},
{
"name": "Ethernet24"
},
{
"name": "Ethernet28"
},
{
"name": "Ethernet32"
},
{
"name": "Ethernet36"
},
{
"name": "Ethernet40"
},
{
"name": "Ethernet44"
},
{
"name": "Ethernet48"
},
{
"name": "Ethernet52"
},
{
"name": "Ethernet56"
},
{
"name": "Ethernet60"
},
{
"name": "Ethernet64"
},
{
"name": "Ethernet68"
},
{
"name": "Ethernet72"
},
{
"name": "Ethernet76"
},
{
"name": "Ethernet80"
},
{
"name": "Ethernet84"
},
{
"name": "Ethernet88"
},
{
"name": "Ethernet92"
},
{
"name": "Ethernet96"
},
{
"name": "Ethernet100"
},
{
"name": "Ethernet104"
},
{
"name": "Ethernet108"
},
{
"name": "Ethernet112"
},
{
"name": "Ethernet116"
},
{
"name": "Ethernet120"
}
]
},
"interfaces": { "interfaces": {
"Ethernet0": { "Ethernet0": {
"index": "1,1,1,1", "index": "1,1,1,1",

View File

@ -17,7 +17,6 @@ except ImportError as e:
raise ImportError(str(e) + "- required module not found") raise ImportError(str(e) + "- required module not found")
NUM_FAN_TRAY = 5 NUM_FAN_TRAY = 5
NUM_FAN = 2
NUM_PSU = 2 NUM_PSU = 2
NUM_THERMAL = 5 NUM_THERMAL = 5
NUM_SFP = 32 NUM_SFP = 32
@ -28,6 +27,7 @@ REBOOT_CAUSE_FILE = "reboot-cause.txt"
PREV_REBOOT_CAUSE_FILE = "previous-reboot-cause.txt" PREV_REBOOT_CAUSE_FILE = "previous-reboot-cause.txt"
GETREG_PATH = "/sys/devices/platform/dx010_cpld/getreg" GETREG_PATH = "/sys/devices/platform/dx010_cpld/getreg"
HOST_CHK_CMD = "docker > /dev/null 2>&1" HOST_CHK_CMD = "docker > /dev/null 2>&1"
STATUS_LED_PATH = "/sys/devices/platform/leds_dx010/leds/dx010:green:stat/brightness"
class Chassis(ChassisBase): class Chassis(ChassisBase):
@ -40,12 +40,10 @@ class Chassis(ChassisBase):
self.__initialize_eeprom() self.__initialize_eeprom()
self.is_host = self._api_helper.is_host() self.is_host = self._api_helper.is_host()
if not self.is_host: self.__initialize_fan()
self.__initialize_fan() self.__initialize_psu()
self.__initialize_psu() self.__initialize_thermals()
self.__initialize_thermals() self.__initialize_components()
else:
self.__initialize_components()
def __initialize_sfp(self): def __initialize_sfp(self):
sfputil_helper = SfpUtilHelper() sfputil_helper = SfpUtilHelper()
@ -54,8 +52,7 @@ class Chassis(ChassisBase):
from sonic_platform.sfp import Sfp from sonic_platform.sfp import Sfp
for index in range(0, NUM_SFP): for index in range(0, NUM_SFP):
name_idx = 0 if index+1 == NUM_SFP else index+1 sfp = Sfp(index, sfputil_helper.logical[index])
sfp = Sfp(index, sfputil_helper.logical[name_idx])
self._sfp_list.append(sfp) self._sfp_list.append(sfp)
self.sfp_module_initialized = True self.sfp_module_initialized = True
@ -66,11 +63,11 @@ class Chassis(ChassisBase):
self._psu_list.append(psu) self._psu_list.append(psu)
def __initialize_fan(self): def __initialize_fan(self):
from sonic_platform.fan import Fan from sonic_platform.fan_drawer import FanDrawer
for fant_index in range(0, NUM_FAN_TRAY): for i in range(NUM_FAN_TRAY):
for fan_index in range(0, NUM_FAN): fandrawer = FanDrawer(i)
fan = Fan(fant_index, fan_index) self._fan_drawer_list.append(fandrawer)
self._fan_list.append(fan) self._fan_list.extend(fandrawer._fan_list)
def __initialize_thermals(self): def __initialize_thermals(self):
from sonic_platform.thermal import Thermal from sonic_platform.thermal import Thermal
@ -90,7 +87,9 @@ class Chassis(ChassisBase):
self._component_list.append(component) self._component_list.append(component)
def __get_air_flow(self): def __get_air_flow(self):
air_flow_path = '/usr/share/sonic/device/{}/fan_airflow'.format(self._api_helper.platform) if self.is_host else '/usr/share/sonic/platform/fan_airflow' air_flow_path = '/usr/share/sonic/device/{}/fan_airflow'.format(
self._api_helper.platform) \
if self.is_host else '/usr/share/sonic/platform/fan_airflow'
air_flow = self._api_helper.read_one_line_file(air_flow_path) air_flow = self._api_helper.read_one_line_file(air_flow_path)
return air_flow or 'B2F' return air_flow or 'B2F'
@ -155,7 +154,6 @@ class Chassis(ChassisBase):
return prev_reboot_cause return prev_reboot_cause
def get_change_event(self, timeout=0): def get_change_event(self, timeout=0):
""" """
Returns a nested dictionary containing all devices which have Returns a nested dictionary containing all devices which have
@ -231,7 +229,7 @@ class Chassis(ChassisBase):
try: try:
# The index will start from 1 # The index will start from 1
sfp = self._sfp_list[index-1] sfp = self._sfp_list[index - 1]
except IndexError: except IndexError:
sys.stderr.write("SFP index {} out of range (1-{})\n".format( sys.stderr.write("SFP index {} out of range (1-{})\n".format(
index, len(self._sfp_list))) index, len(self._sfp_list)))
@ -254,6 +252,10 @@ class Chassis(ChassisBase):
return self._watchdog return self._watchdog
def get_thermal_manager(self):
from .thermal_manager import ThermalManager
return ThermalManager
############################################################## ##############################################################
###################### Device methods ######################## ###################### Device methods ########################
############################################################## ##############################################################
@ -298,6 +300,53 @@ class Chassis(ChassisBase):
""" """
return True return True
def get_thermal_manager(self): def get_position_in_parent(self):
from .thermal_manager import ThermalManager """
return ThermalManager 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 False
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
"""
set_status_str = {
self.STATUS_LED_COLOR_GREEN: '1',
self.STATUS_LED_COLOR_OFF: '0'
}.get(color, None)
if not set_status_str:
return False
return self._api_helper.write_txt_file(STATUS_LED_PATH, set_status_str)
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._api_helper.read_txt_file(STATUS_LED_PATH)
status_str = {
'255': self.STATUS_LED_COLOR_GREEN,
'0': self.STATUS_LED_COLOR_OFF
}.get(status, None)
return status_str

View File

@ -128,3 +128,61 @@ class Component(ComponentBase):
# install_command = "afulnx_64 %s /p /b /n /x /r" % image_path # install_command = "afulnx_64 %s /p /b /n /x /r" % image_path
return self.__run_command(install_command) return self.__run_command(install_command)
##############################################################
###################### Device methods ########################
##############################################################
def get_presence(self):
"""
Retrieves the presence of the FAN
Returns:
bool: True if FAN is present, False if not
"""
return True
def get_model(self):
"""
Retrieves the model number (or part number) of the device
Returns:
string: Model/part number of device
"""
return 'N/A'
def get_serial(self):
"""
Retrieves the serial number of the device
Returns:
string: Serial number of device
"""
return '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 True
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 False

View File

@ -8,24 +8,22 @@
############################################################################# #############################################################################
try: try:
import glob
import os import os
import sys import sys
import re import re
from array import array
if sys.version_info.major == 3: if sys.version_info.major == 3:
from io import StringIO from io import StringIO
else: else:
from cStringIO import StringIO from cStringIO import StringIO
from sonic_platform_base.sonic_eeprom import eeprom_dts
from sonic_platform_base.sonic_eeprom import eeprom_tlvinfo from sonic_platform_base.sonic_eeprom import eeprom_tlvinfo
except ImportError as e: except ImportError as e:
raise ImportError(str(e) + "- required module not found") raise ImportError(str(e) + "- required module not found")
CACHE_ROOT = '/var/cache/sonic/decode-syseeprom' CACHE_ROOT = '/var/cache/sonic/decode-syseeprom'
CACHE_FILE = 'syseeprom_cache' CACHE_FILE = 'syseeprom_cache'
NULL = 'N/A'
class Tlv(eeprom_tlvinfo.TlvInfoDecoder): class Tlv(eeprom_tlvinfo.TlvInfoDecoder):
@ -52,7 +50,7 @@ class Tlv(eeprom_tlvinfo.TlvInfoDecoder):
value = match.group(3).rstrip('\0') value = match.group(3).rstrip('\0')
_eeprom_info_dict[idx] = value _eeprom_info_dict[idx] = value
except: except BaseException:
pass pass
return _eeprom_info_dict return _eeprom_info_dict
@ -61,7 +59,7 @@ class Tlv(eeprom_tlvinfo.TlvInfoDecoder):
sys.stdout = StringIO() sys.stdout = StringIO()
try: try:
self.read_eeprom_db() self.read_eeprom_db()
except: except BaseException:
decode_output = sys.stdout.getvalue() decode_output = sys.stdout.getvalue()
sys.stdout = original_stdout sys.stdout = original_stdout
return self.__parse_output(decode_output) return self.__parse_output(decode_output)
@ -73,7 +71,7 @@ class Tlv(eeprom_tlvinfo.TlvInfoDecoder):
if not os.path.exists(CACHE_ROOT): if not os.path.exists(CACHE_ROOT):
try: try:
os.makedirs(CACHE_ROOT) os.makedirs(CACHE_ROOT)
except: except BaseException:
pass pass
# #
@ -82,7 +80,7 @@ class Tlv(eeprom_tlvinfo.TlvInfoDecoder):
# #
try: try:
self.set_cache_name(os.path.join(CACHE_ROOT, CACHE_FILE)) self.set_cache_name(os.path.join(CACHE_ROOT, CACHE_FILE))
except: except BaseException:
pass pass
e = self.read_eeprom() e = self.read_eeprom()
@ -91,7 +89,7 @@ class Tlv(eeprom_tlvinfo.TlvInfoDecoder):
try: try:
self.update_cache(e) self.update_cache(e)
except: except BaseException:
pass pass
self.decode_eeprom(e) self.decode_eeprom(e)
@ -104,11 +102,40 @@ class Tlv(eeprom_tlvinfo.TlvInfoDecoder):
return self.__parse_output(decode_output) 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): def get_eeprom(self):
return self._eeprom return self._valid_tlv(self._eeprom)
def get_pn(self):
return self._eeprom.get('0x22', NULL)
def get_serial(self): def get_serial(self):
return self._eeprom.get('0x23', "Undefined.") return self._eeprom.get('0x23', NULL)
def get_mac(self): def get_mac(self):
return self._eeprom.get('0x24', "Undefined.") return self._eeprom.get('0x24', NULL)

View File

@ -6,7 +6,7 @@
# #
############################################################################# #############################################################################
import json from __future__ import division
import math import math
import os.path import os.path
@ -25,6 +25,7 @@ EMC2305_FAN_TARGET = "fan{}_target"
EMC2305_FAN_INPUT = "pwm{}" EMC2305_FAN_INPUT = "pwm{}"
FAN_NAME_LIST = ["FAN-1F", "FAN-1R", "FAN-2F", "FAN-2R", FAN_NAME_LIST = ["FAN-1F", "FAN-1R", "FAN-2F", "FAN-2R",
"FAN-3F", "FAN-3R", "FAN-4F", "FAN-4R", "FAN-5F", "FAN-5R"] "FAN-3F", "FAN-3R", "FAN-4F", "FAN-4R", "FAN-5F", "FAN-5R"]
FAN_SPEED_TOLERANCE = 10
PSU_FAN_MAX_RPM = 11000 PSU_FAN_MAX_RPM = 11000
PSU_HWMON_PATH = "/sys/bus/i2c/devices/i2c-{0}/{0}-00{1}/hwmon" PSU_HWMON_PATH = "/sys/bus/i2c/devices/i2c-{0}/{0}-00{1}/hwmon"
PSU_I2C_MAPPING = { PSU_I2C_MAPPING = {
@ -82,7 +83,7 @@ class Fan(FanBase):
try: try:
with open(file_path, 'w') as fd: with open(file_path, 'w') as fd:
fd.write(str(value)) fd.write(str(value))
except: except Exception:
return False return False
return True return True
@ -97,7 +98,8 @@ class Fan(FanBase):
def __get_gpio_base(self): def __get_gpio_base(self):
for r in os.listdir(GPIO_DIR): for r in os.listdir(GPIO_DIR):
label_path = os.path.join(GPIO_DIR, r, "label") label_path = os.path.join(GPIO_DIR, r, "label")
if "gpiochip" in r and GPIO_LABEL in self._api_helper.read_txt_file(label_path): if "gpiochip" in r and GPIO_LABEL in \
self._api_helper.read_txt_file(label_path):
return int(r[8:], 10) return int(r[8:], 10)
return 216 # Reserve return 216 # Reserve
@ -148,7 +150,6 @@ class Fan(FanBase):
self.psu_hwmon_path, fan_speed_sysfs_name) self.psu_hwmon_path, fan_speed_sysfs_name)
fan_speed_rpm = self._api_helper.read_txt_file( fan_speed_rpm = self._api_helper.read_txt_file(
fan_speed_sysfs_path) or 0 fan_speed_sysfs_path) or 0
fan_speed_raw = float(fan_speed_rpm)/PSU_FAN_MAX_RPM * 100
speed = math.ceil(float(fan_speed_rpm) * 100 / PSU_FAN_MAX_RPM) speed = math.ceil(float(fan_speed_rpm) * 100 / PSU_FAN_MAX_RPM)
elif self.get_presence(): elif self.get_presence():
chip = self.emc2305_chip_mapping[self.fan_index] chip = self.emc2305_chip_mapping[self.fan_index]
@ -176,7 +177,19 @@ class Fan(FanBase):
0 : when PWM mode is use 0 : when PWM mode is use
pwm : when pwm mode is not use pwm : when pwm mode is not use
""" """
return 'N/A' target = NULL_VAL
if not self.is_psu_fan:
chip = self.emc2305_chip_mapping[self.fan_index]
device = chip['device']
fan_index = chip['index_map']
sysfs_path = "%s%s/%s" % (
EMC2305_PATH, device, EMC2305_FAN_PWM)
sysfs_path = sysfs_path.format(fan_index[self.fan_tray_index])
pwm = self._api_helper.read_txt_file(sysfs_path)
target = round(int(pwm) / 255 * 100.0)
return target
def get_speed_tolerance(self): def get_speed_tolerance(self):
""" """
@ -185,7 +198,7 @@ class Fan(FanBase):
An integer, the percentage of variance from target speed which is An integer, the percentage of variance from target speed which is
considered tolerable considered tolerable
""" """
return 10 return FAN_SPEED_TOLERANCE
def set_speed(self, speed): def set_speed(self, speed):
""" """
@ -225,41 +238,67 @@ class Fan(FanBase):
bool: True if status LED state is set successfully, False if not bool: True if status LED state is set successfully, False if not
""" """
set_status_led = False set_status_led = False
s1_gpio = self.dx010_fan_gpio[self.fan_tray_index+1]['color']['red']
s2_gpio = self.dx010_fan_gpio[self.fan_tray_index+1]['color']['green']
if not self.is_psu_fan: if not self.is_psu_fan:
s1, s2 = False, False
try: try:
if color == self.STATUS_LED_COLOR_GREEN: if color == self.STATUS_LED_COLOR_GREEN:
s1 = self.__set_gpio_value( s1 = self.__set_gpio_value(s1_gpio, 1)
self.dx010_fan_gpio[self.fan_tray_index+1]['color']['red'], 1) s2 = self.__set_gpio_value(s2_gpio, 0)
s2 = self.__set_gpio_value(
self.dx010_fan_gpio[self.fan_tray_index+1]['color']['green'], 0)
elif color == self.STATUS_LED_COLOR_RED: elif color == self.STATUS_LED_COLOR_RED:
s1 = self.__set_gpio_value( s1 = self.__set_gpio_value(s1_gpio, 0)
self.dx010_fan_gpio[self.fan_tray_index+1]['color']['red'], 0) s2 = self.__set_gpio_value(s2_gpio, 1)
s2 = self.__set_gpio_value(
self.dx010_fan_gpio[self.fan_tray_index+1]['color']['green'], 1)
elif color == self.STATUS_LED_COLOR_OFF: elif color == self.STATUS_LED_COLOR_OFF:
s1 = self.__set_gpio_value( s1 = self.__set_gpio_value(s1_gpio, 1)
self.dx010_fan_gpio[self.fan_tray_index+1]['color']['red'], 1) s2 = self.__set_gpio_value(s2_gpio, 1)
s2 = self.__set_gpio_value(
self.dx010_fan_gpio[self.fan_tray_index+1]['color']['green'], 1) elif color == self.STATUS_LED_COLOR_AMBER:
s1 = self.__set_gpio_value(s1_gpio, 0)
s2 = self.__set_gpio_value(s2_gpio, 0)
else:
s1, s2 = True, True
set_status_led = s1 and s2 set_status_led = s1 and s2
return set_status_led
except IOError: except IOError:
return False return False
return set_status_led return set_status_led
def get_status_led(self):
"""
Gets the state of the fan status LED
Returns:
A string, one of the predefined STATUS_LED_COLOR_* strings above
"""
s1 = self.__get_gpio_value(
self.dx010_fan_gpio[self.fan_tray_index+1]['color']['red'])
s2 = self.__get_gpio_value(
self.dx010_fan_gpio[self.fan_tray_index+1]['color']['green'])
return {
'10': self.STATUS_LED_COLOR_GREEN,
'01': self.STATUS_LED_COLOR_RED,
'00': self.STATUS_LED_COLOR_AMBER
}.get(s1+s2, self.STATUS_LED_COLOR_OFF)
##############################################################
###################### Device methods ########################
##############################################################
def get_name(self): def get_name(self):
""" """
Retrieves the name of the device Retrieves the name of the device
Returns: Returns:
string: The name of the device string: The name of the device
""" """
fan_name = FAN_NAME_LIST[self.fan_tray_index*2 + self.fan_index] if not self.is_psu_fan else "PSU-{} FAN-{}".format( fan_name = FAN_NAME_LIST[self.fan_tray_index*2 + self.fan_index] \
self.psu_index+1, self.fan_index+1) if not self.is_psu_fan \
else "PSU-{} FAN-{}".format(self.psu_index+1, self.fan_index+1)
return fan_name return fan_name
@ -321,3 +360,24 @@ class Fan(FanBase):
status = self._api_helper.read_one_line_file(sysfs_path) status = self._api_helper.read_one_line_file(sysfs_path)
return False if int(status) != 0 else True return False if int(status) != 0 else True
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 (self.fan_tray_index*2 + self.fan_index + 1) \
if not self.is_psu_fan else (self.fan_index+1)
def is_replaceable(self):
"""
Indicate whether this device is replaceable.
Returns:
bool: True if it is replaceable.
"""
return True if not self.is_psu_fan else False

View File

@ -0,0 +1,108 @@
#!/usr/bin/env python
#############################################################################
# Celestica
#
# Module contains an implementation of SONiC Platform Base API and
# provides the the Fan-Drawers' information available in the platform
#
#############################################################################
try:
from sonic_platform_base.fan_drawer_base import FanDrawerBase
except ImportError as e:
raise ImportError(str(e) + "- required module not found")
NUM_FAN = 2
class FanDrawer(FanDrawerBase):
def __init__(self, fantray_index):
FanDrawerBase.__init__(self)
self._index = fantray_index + 1
self._init_fan(fantray_index)
def _init_fan(self, fantray_index):
from sonic_platform.fan import Fan
for index in range(NUM_FAN):
fan = Fan(fantray_index, index)
self._fan_list.append(fan)
def set_status_led(self, color):
"""
Sets the state of the fan drawer status LED
Args:
color: A string representing the color with which to set the
fan drawer status LED
Returns:
bool: True if status LED state is set successfully, False if not
"""
return self._fan_list[0].set_status_led(color)
def get_status_led(self, color=None):
"""
Gets the state of the fan drawer LED
Returns:
A string, one of the predefined STATUS_LED_COLOR_* strings above
"""
return self._fan_list[0].get_status_led()
##############################################################
###################### Device methods ########################
##############################################################
def get_name(self):
"""
Retrieves the name of the device
Returns:
string: The name of the device
"""
return "Drawer{}".format(self._index)
def get_presence(self):
"""
Retrieves the presence of the device
Returns:
bool: True if device is present, False if not
"""
return self._fan_list[0].get_presence()
def get_model(self):
"""
Retrieves the model number (or part number) of the device
Returns:
string: Model/part number of device
"""
return self._fan_list[0].get_model()
def get_serial(self):
"""
Retrieves the serial number of the device
Returns:
string: Serial number of device
"""
return self._fan_list[0].get_serial()
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._fan_list[0].get_status()
def get_position_in_parent(self):
"""
Retrieves 1-based relative physical position in parent device
Returns:
integer: The 1-based relative physical position in parent device
"""
return self._index
def is_replaceable(self):
"""
Indicate whether this device is replaceable.
Returns:
bool: True if it is replaceable.
"""
return True

View File

@ -7,7 +7,6 @@
############################################################################# #############################################################################
import os import os
import sonic_platform
try: try:
from sonic_platform_base.psu_base import PsuBase from sonic_platform_base.psu_base import PsuBase
@ -16,6 +15,9 @@ try:
except ImportError as e: except ImportError as e:
raise ImportError(str(e) + "- required module not found") raise ImportError(str(e) + "- required module not found")
TLV_ATTR_TYPE_MODEL = 2
TLV_ATTR_TYPE_SERIAL = 5
PSU_EEPROM_PATH = "/sys/bus/i2c/devices/{}-00{}/eeprom"
GREEN_LED_PATH = "/sys/devices/platform/leds_dx010/leds/dx010:green:p-{}/brightness" GREEN_LED_PATH = "/sys/devices/platform/leds_dx010/leds/dx010:green:p-{}/brightness"
HWMON_PATH = "/sys/bus/i2c/devices/i2c-{0}/{0}-00{1}/hwmon" HWMON_PATH = "/sys/bus/i2c/devices/i2c-{0}/{0}-00{1}/hwmon"
GPIO_DIR = "/sys/class/gpio" GPIO_DIR = "/sys/class/gpio"
@ -25,11 +27,13 @@ PSU_NUM_FAN = [1, 1]
PSU_I2C_MAPPING = { PSU_I2C_MAPPING = {
0: { 0: {
"num": 10, "num": 10,
"addr": "5a" "addr": "5a",
"eeprom_addr": "52"
}, },
1: { 1: {
"num": 11, "num": 11,
"addr": "5b" "addr": "5b",
"eeprom_addr": "53"
}, },
} }
@ -41,7 +45,7 @@ class Psu(PsuBase):
PsuBase.__init__(self) PsuBase.__init__(self)
self.index = psu_index self.index = psu_index
self._api_helper = APIHelper() self._api_helper = APIHelper()
self.green_led_path = GREEN_LED_PATH.format(self.index+1) self.green_led_path = GREEN_LED_PATH.format(self.index + 1)
self.dx010_psu_gpio = [ self.dx010_psu_gpio = [
{'base': self.__get_gpio_base()}, {'base': self.__get_gpio_base()},
{'prs': 27, 'status': 22}, {'prs': 27, 'status': 22},
@ -50,6 +54,7 @@ class Psu(PsuBase):
self.i2c_num = PSU_I2C_MAPPING[self.index]["num"] self.i2c_num = PSU_I2C_MAPPING[self.index]["num"]
self.i2c_addr = PSU_I2C_MAPPING[self.index]["addr"] self.i2c_addr = PSU_I2C_MAPPING[self.index]["addr"]
self.hwmon_path = HWMON_PATH.format(self.i2c_num, self.i2c_addr) self.hwmon_path = HWMON_PATH.format(self.i2c_num, self.i2c_addr)
self.eeprom_addr = PSU_EEPROM_PATH.format(self.i2c_num, PSU_I2C_MAPPING[self.index]["eeprom_addr"])
for fan_index in range(0, PSU_NUM_FAN[self.index]): for fan_index in range(0, PSU_NUM_FAN[self.index]):
fan = Fan(fan_index, 0, is_psu_fan=True, psu_index=self.index) fan = Fan(fan_index, 0, is_psu_fan=True, psu_index=self.index)
self._fan_list.append(fan) self._fan_list.append(fan)
@ -71,11 +76,37 @@ class Psu(PsuBase):
def __get_gpio_value(self, pinnum): def __get_gpio_value(self, pinnum):
gpio_base = self.dx010_psu_gpio[0]['base'] gpio_base = self.dx010_psu_gpio[0]['base']
gpio_dir = GPIO_DIR + '/gpio' + str(gpio_base+pinnum) gpio_dir = GPIO_DIR + '/gpio' + str(gpio_base + pinnum)
gpio_file = gpio_dir + "/value" gpio_file = gpio_dir + "/value"
retval = self._api_helper.read_txt_file(gpio_file) retval = self._api_helper.read_txt_file(gpio_file)
return retval.rstrip('\r\n') return retval.rstrip('\r\n')
def read_fru(self, path, attr_type):
content = []
attr_idx = 0
attr_length = 0
if(os.path.exists(path)):
with open(path, 'r', encoding='unicode_escape') as f:
content = f.read()
target_offset = ord(content[4])
target_offset *= 8 # spec defined: offset are in multiples of 8 bytes
attr_idx = target_offset + 3
for i in range(1, attr_type):
if attr_idx > len(content):
raise SyntaxError
attr_length = (ord(content[attr_idx])) & (0x3f)
attr_idx += (attr_length + 1)
attr_length = (ord(content[attr_idx])) & (0x3f)
attr_idx += 1
else:
print("[PSU] Can't find path to eeprom : %s" % path)
return SyntaxError
return content[attr_idx:attr_idx + attr_length]
def get_voltage(self): def get_voltage(self):
""" """
Retrieves current PSU voltage output Retrieves current PSU voltage output
@ -209,7 +240,7 @@ class Psu(PsuBase):
Returns: Returns:
bool: True if PSU is present, False if not bool: True if PSU is present, False if not
""" """
raw = self.__get_gpio_value(self.dx010_psu_gpio[self.index+1]['prs']) raw = self.__get_gpio_value(self.dx010_psu_gpio[self.index + 1]['prs'])
return int(raw, 10) == 0 return int(raw, 10) == 0
def get_status(self): def get_status(self):
@ -219,5 +250,142 @@ class Psu(PsuBase):
A boolean value, True if device is operating properly, False if not A boolean value, True if device is operating properly, False if not
""" """
raw = self.__get_gpio_value( raw = self.__get_gpio_value(
self.dx010_psu_gpio[self.index+1]['status']) self.dx010_psu_gpio[self.index + 1]['status'])
return int(raw, 10) == 1 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
Returns:
A float number of current temperature in Celsius up to nearest thousandth
of one degree Celsius, e.g. 30.125
there are three temp sensors , we choose one of them
"""
psu_temperature = None
temperature_name = "temp{}_input"
temperature_label = "vout1"
vout_label_path = self.__search_file_by_contain(
self.hwmon_path, temperature_label, "in")
if vout_label_path:
dir_name = os.path.dirname(vout_label_path)
basename = os.path.basename(vout_label_path)
in_num = ''.join(list(filter(str.isdigit, basename)))
temp_path = os.path.join(
dir_name, temperature_name.format(in_num))
vout_val = self._api_helper.read_txt_file(temp_path)
psu_temperature = float(vout_val) / 1000
return psu_temperature
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
there are three temp sensors , we choose one of them
"""
psu_temperature = None
temperature_name = "temp{}_max"
temperature_label = "vout1"
vout_label_path = self.__search_file_by_contain(
self.hwmon_path, temperature_label, "in")
if vout_label_path:
dir_name = os.path.dirname(vout_label_path)
basename = os.path.basename(vout_label_path)
in_num = ''.join(list(filter(str.isdigit, basename)))
temp_path = os.path.join(
dir_name, temperature_name.format(in_num))
vout_val = self._api_helper.read_txt_file(temp_path)
psu_temperature = float(vout_val) / 1000
return psu_temperature
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
"""
psu_voltage = 0.0
voltage_name = "in{}_crit"
voltage_label = "vout1"
vout_label_path = self.__search_file_by_contain(
self.hwmon_path, voltage_label, "in")
if vout_label_path:
dir_name = os.path.dirname(vout_label_path)
basename = os.path.basename(vout_label_path)
in_num = ''.join(list(filter(str.isdigit, basename)))
vout_path = os.path.join(
dir_name, voltage_name.format(in_num))
vout_val = self._api_helper.read_txt_file(vout_path)
psu_voltage = float(vout_val) / 1000
return psu_voltage
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
"""
psu_voltage = 0.0
voltage_name = "in{}_lcrit"
voltage_label = "vout1"
vout_label_path = self.__search_file_by_contain(
self.hwmon_path, voltage_label, "in")
if vout_label_path:
dir_name = os.path.dirname(vout_label_path)
basename = os.path.basename(vout_label_path)
in_num = ''.join(list(filter(str.isdigit, basename)))
vout_path = os.path.join(
dir_name, voltage_name.format(in_num))
vout_val = self._api_helper.read_txt_file(vout_path)
psu_voltage = float(vout_val) / 1000
return psu_voltage

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,6 @@
############################################################################# #############################################################################
import os import os
import re
import os.path import os.path
try: try:
@ -19,14 +18,18 @@ except ImportError as e:
THERMAL_INFO = { THERMAL_INFO = {
0: { 0: {
"F2B_max": 50, "F2B_max": 50,
"F2B_max_crit": 75,
"B2F_max": 55, "B2F_max": 55,
"B2F_max_crit": 75,
"postion": "asic", "postion": "asic",
"name": "Front-panel temp sensor 1", "name": "Front-panel temp sensor 1",
"i2c_path": "i2c-5/5-0048/hwmon/hwmon1", # u4 system-inlet "i2c_path": "i2c-5/5-0048/hwmon/hwmon1", # u4 system-inlet
}, },
1: { 1: {
"F2B_max": 50, "F2B_max": 50,
"F2B_max_crit": 75,
"B2F_max": 55, "B2F_max": 55,
"B2F_max_crit": 75,
"postion": "asic", "postion": "asic",
"name": "Front-panel temp sensor 2", "name": "Front-panel temp sensor 2",
"i2c_path": "i2c-6/6-0049/hwmon/hwmon2", # u2 system-inlet "i2c_path": "i2c-6/6-0049/hwmon/hwmon2", # u2 system-inlet
@ -51,7 +54,9 @@ THERMAL_INFO = {
}, },
4: { 4: {
"F2B_max": 70, "F2B_max": 70,
"F2B_max_crit": 75,
"B2F_max": 55, "B2F_max": 55,
"B2F_max_crit": 75,
"postion": "cpu", "postion": "cpu",
"name": "Rear-panel temp sensor 2", "name": "Rear-panel temp sensor 2",
"i2c_path": "i2c-15/15-004e/hwmon/hwmon5" # u9201 system-outlet "i2c_path": "i2c-15/15-004e/hwmon/hwmon5" # u9201 system-outlet
@ -72,8 +77,7 @@ class Thermal(ThermalBase):
self._api_helper = APIHelper() self._api_helper = APIHelper()
self._airflow = airflow self._airflow = airflow
self._thermal_info = THERMAL_INFO[self.index] self._thermal_info = THERMAL_INFO[self.index]
self._hwmon_path = "{}/{}".format(I2C_ADAPTER_PATH, self._hwmon_path = "{}/{}".format(I2C_ADAPTER_PATH, self._thermal_info["i2c_path"])
self._thermal_info["i2c_path"])
self.name = self.get_name() self.name = self.get_name()
self.postion = self._thermal_info["postion"] self.postion = self._thermal_info["postion"]
self.ss_index = 1 self.ss_index = 1
@ -110,8 +114,9 @@ class Thermal(ThermalBase):
A float number, the high threshold temperature of thermal in Celsius A float number, the high threshold temperature of thermal in Celsius
up to nearest thousandth of one degree Celsius, e.g. 30.125 up to nearest thousandth of one degree Celsius, e.g. 30.125
""" """
max_crit_key = '{}_max'.format(self._airflow) temp_file = "temp{}_max".format(self.ss_index)
return self._thermal_info.get(max_crit_key, None) temp = float(self.__get_temp(temp_file))
return temp
def get_low_threshold(self): def get_low_threshold(self):
""" """
@ -120,7 +125,7 @@ class Thermal(ThermalBase):
A float number, the low threshold temperature of thermal in Celsius A float number, the low threshold temperature of thermal in Celsius
up to nearest thousandth of one degree Celsius, e.g. 30.125 up to nearest thousandth of one degree Celsius, e.g. 30.125
""" """
return 0.0 return 0.001
def set_high_threshold(self, temperature): def set_high_threshold(self, temperature):
""" """
@ -133,31 +138,33 @@ class Thermal(ThermalBase):
""" """
temp_file = "temp{}_max".format(self.ss_index) temp_file = "temp{}_max".format(self.ss_index)
is_set = self.__set_threshold(temp_file, int(temperature*1000)) is_set = self.__set_threshold(temp_file, int(temperature*1000))
file_set = False file_set = True
if is_set: if self._api_helper.is_host():
try: file_set = False
with open(self.SS_CONFIG_PATH, 'r+') as f: if is_set:
content = f.readlines() try:
f.seek(0) with open(self.SS_CONFIG_PATH, 'r+') as f:
ss_found = False content = f.readlines()
for idx, val in enumerate(content): f.seek(0)
if self.name in val: ss_found = False
ss_found = True for idx, val in enumerate(content):
elif ss_found and temp_file in val: if self.name in val:
content[idx] = " set {} {}\n".format( ss_found = True
temp_file, temperature) elif ss_found and temp_file in val:
f.writelines(content) content[idx] = " set {} {}\n".format(
file_set = True temp_file, temperature)
break f.writelines(content)
except IOError: file_set = True
file_set = False break
except IOError as err:
file_set = False
return is_set & file_set return is_set & file_set
def set_low_threshold(self, temperature): def set_low_threshold(self, temperature):
""" """
Sets the low threshold temperature of thermal Sets the low threshold temperature of thermal
Args : Args :
temperature: A float number up to nearest thousandth of one degree Celsius, temperature: A float number up to nearest thousandth of one degree Celsius,
e.g. 30.125 e.g. 30.125
Returns: Returns:
@ -173,7 +180,10 @@ class Thermal(ThermalBase):
up to nearest thousandth of one degree Celsius, e.g. 30.125 up to nearest thousandth of one degree Celsius, e.g. 30.125
""" """
max_crit_key = '{}_max_crit'.format(self._airflow) max_crit_key = '{}_max_crit'.format(self._airflow)
return self._thermal_info.get(max_crit_key, None) max_crit_threshold = self._thermal_info.get(max_crit_key, None)
if max_crit_threshold is not None:
max_crit_threshold = float(max_crit_threshold)
return max_crit_threshold
def get_low_critical_threshold(self): def get_low_critical_threshold(self):
""" """
@ -182,7 +192,7 @@ class Thermal(ThermalBase):
A float number, the low critical threshold temperature of thermal in Celsius A float number, the low critical threshold temperature of thermal in Celsius
up to nearest thousandth of one degree Celsius, e.g. 30.125 up to nearest thousandth of one degree Celsius, e.g. 30.125
""" """
return 0.0 return 0.001
def get_name(self): def get_name(self):
""" """
@ -234,3 +244,21 @@ class Thermal(ThermalBase):
raw_txt = self.__read_txt_file(fault_file_path) raw_txt = self.__read_txt_file(fault_file_path)
return int(raw_txt) == 0 return int(raw_txt) == 0
def is_replaceable(self):
"""
Retrieves whether thermal module is replaceable
Returns:
A boolean value, True if replaceable, False if not
"""
return False
def get_position_in_parent(self):
"""
Retrieves the thermal position information
Returns:
A int value, 0 represent ASIC thermal, 1 represent CPU thermal info
"""
if self.postion == "cpu":
return 1
return 0

View File

@ -135,6 +135,9 @@ class Watchdog(WatchdogBase):
ret = WDT_COMMON_ERROR ret = WDT_COMMON_ERROR
if seconds < 0: if seconds < 0:
return ret return ret
if seconds > 16779:
return ret
try: try:
if self.timeout != seconds: if self.timeout != seconds:

View File

@ -89,10 +89,19 @@ start)
echo emc2305 0x2e > /sys/bus/i2c/devices/i2c-13/new_device echo emc2305 0x2e > /sys/bus/i2c/devices/i2c-13/new_device
echo emc2305 0x4d > /sys/bus/i2c/devices/i2c-13/new_device echo emc2305 0x4d > /sys/bus/i2c/devices/i2c-13/new_device
echo 24c02 0x50 > /sys/bus/i2c/devices/i2c-18/new_device
echo 24c02 0x50 > /sys/bus/i2c/devices/i2c-19/new_device
echo 24c02 0x50 > /sys/bus/i2c/devices/i2c-20/new_device
echo 24c02 0x50 > /sys/bus/i2c/devices/i2c-21/new_device
echo 24c02 0x50 > /sys/bus/i2c/devices/i2c-22/new_device
# Attach PSUs # Attach PSUs
echo dps460 0x5a > /sys/bus/i2c/devices/i2c-10/new_device echo dps460 0x5a > /sys/bus/i2c/devices/i2c-10/new_device
echo dps460 0x5b > /sys/bus/i2c/devices/i2c-11/new_device echo dps460 0x5b > /sys/bus/i2c/devices/i2c-11/new_device
echo 24c02 0x52 > /sys/bus/i2c/devices/i2c-10/new_device
echo 24c02 0x53 > /sys/bus/i2c/devices/i2c-11/new_device
# Attach PCA9506 GPIO expander for 40 pins # Attach PCA9506 GPIO expander for 40 pins
echo pca9505 0x20 > /sys/bus/i2c/devices/i2c-17/new_device echo pca9505 0x20 > /sys/bus/i2c/devices/i2c-17/new_device
@ -117,16 +126,16 @@ start)
export_gpio 27 "in" # PSU L ABS export_gpio 27 "in" # PSU L ABS
export_gpio 28 "in" # PSU R ABS export_gpio 28 "in" # PSU R ABS
export_gpio 29 "out" # Fan 1 LED: Red export_gpio 29 "out" # Fan 2 LED: Red
export_gpio 30 "out" # Fan 1 LED: Yellow export_gpio 30 "out" # Fan 2 LED: Green
export_gpio 31 "out" # Fan 2 LED: Red export_gpio 31 "out" # Fan 1 LED: Red
export_gpio 32 "out" # Fan 2 LED: Yellow export_gpio 32 "out" # Fan 1 LED: Green
export_gpio 33 "out" # Fan 3 LED: Red export_gpio 33 "out" # Fan 5 LED: Red
export_gpio 34 "out" # Fan 3 LED: Yellow export_gpio 34 "out" # Fan 5 LED: Green
export_gpio 35 "out" # Fan 4 LED: Red export_gpio 35 "out" # Fan 3 LED: Red
export_gpio 36 "out" # Fan 4 LED: Yellow export_gpio 36 "out" # Fan 3 LED: Green
export_gpio 37 "out" # Fan 5 LED: Red export_gpio 37 "out" # Fan 4 LED: Red
export_gpio 38 "out" # Fan 5 LED: Yellow export_gpio 38 "out" # Fan 4 LED: Green
# Turn off/down lpmod by defult (0 - Normal, 1 - Low Pow) # Turn off/down lpmod by defult (0 - Normal, 1 - Low Pow)
echo 0x00000000 > /sys/devices/platform/dx010_cpld/qsfp_lpmode echo 0x00000000 > /sys/devices/platform/dx010_cpld/qsfp_lpmode

View File

@ -5,4 +5,5 @@ dx010/scripts/fancontrol.sh etc/init.d
dx010/scripts/fancontrol.service lib/systemd/system dx010/scripts/fancontrol.service lib/systemd/system
services/fancontrol/fancontrol usr/local/bin services/fancontrol/fancontrol usr/local/bin
dx010/modules/sonic_platform-1.0-py2-none-any.whl usr/share/sonic/device/x86_64-cel_seastone-r0 dx010/modules/sonic_platform-1.0-py2-none-any.whl usr/share/sonic/device/x86_64-cel_seastone-r0
dx010/modules/sonic_platform-1.0-py3-none-any.whl usr/share/sonic/device/x86_64-cel_seastone-r0
services/platform_api/platform_api_mgnt.sh usr/local/bin services/platform_api/platform_api_mgnt.sh usr/local/bin

View File

@ -4,13 +4,20 @@ PREV_REBOOT_CAUSE="/host/reboot-cause/"
DEVICE="/usr/share/sonic/device" DEVICE="/usr/share/sonic/device"
PLATFORM=$(/usr/local/bin/sonic-cfggen -H -v DEVICE_METADATA.localhost.platform) PLATFORM=$(/usr/local/bin/sonic-cfggen -H -v DEVICE_METADATA.localhost.platform)
FILES=$DEVICE/$PLATFORM/api_files FILES=$DEVICE/$PLATFORM/api_files
PY2_PACK=$DEVICE/$PLATFORM/sonic_platform-1.0-py2-none-any.whl
PY3_PACK=$DEVICE/$PLATFORM/sonic_platform-1.0-py3-none-any.whl
install() { install() {
# Install sonic-platform package # Install python2.7 sonic-platform package
if [ -e $DEVICE/$PLATFORM/sonic_platform-1.0-py2-none-any.whl ]; then if [ -e $PY2_PACK ]; then
pip install $DEVICE/$PLATFORM/sonic_platform-1.0-py2-none-any.whl pip install $PY2_PACK
pip3 install $device/$platform/sonic_platform-1.0-py3-none-any.whl
fi fi
# Install python3 sonic-platform package
if [ -e $PY3_PACK ]; then
pip3 install $PY3_PACK
fi
} }
init() { init() {