Skip to content

Commit e5d8155

Browse files
kevinwangskjleveque
authored andcommitted
[sonic-psud] add a new daemon sonic-psud to platform monitor (#20)
* This new daemon will periodcally read PSU presence and status from sysfs and write to DB * CLI will get the PSU presence and status from DB directly. Signed-off-by: Kevin Wang <[email protected]>
1 parent bc23ab0 commit e5d8155

File tree

2 files changed

+219
-0
lines changed

2 files changed

+219
-0
lines changed

sonic-psud/scripts/psud

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
#!/usr/bin/env python2
2+
3+
"""
4+
Psud
5+
PSU information update daemon for SONiC
6+
This daemon will loop to collect PSU related information and then write the information to state DB.
7+
Currently it is implemented based on old plugins rather than new platform APIs. So the PSU information just
8+
includes three things: number of PSU, PSU presence and PSU status which is supported by old plugins.
9+
The loop interval is PSU_INFO_UPDATE_PERIOD_SECS in seconds.
10+
"""
11+
12+
try:
13+
import getopt
14+
import os
15+
import imp
16+
import signal
17+
import subprocess
18+
import sys
19+
import syslog
20+
import time
21+
from swsscommon import swsscommon
22+
except ImportError, e:
23+
raise ImportError (str(e) + " - required module not found")
24+
25+
#============================= Constants =============================
26+
27+
VERSION = '1.0'
28+
29+
SYSLOG_IDENTIFIER = os.path.basename(__file__)
30+
PLATFORM_SPECIFIC_MODULE_NAME = "psuutil"
31+
PLATFORM_SPECIFIC_CLASS_NAME = "PsuUtil"
32+
33+
# Platform root directory inside docker
34+
PLATFORM_ROOT_DOCKER = "/usr/share/sonic/platform"
35+
SONIC_CFGGEN_PATH = '/usr/local/bin/sonic-cfggen'
36+
HWSKU_KEY = 'DEVICE_METADATA.localhost.hwsku'
37+
PLATFORM_KEY = 'DEVICE_METADATA.localhost.platform'
38+
39+
# Global platform-specific psuutil class instance
40+
platform_psuutil = None
41+
42+
REDIS_HOSTNAME = "localhost"
43+
REDIS_PORT = 6379
44+
REDIS_TIMEOUT_MSECS = 0
45+
46+
PSU_INFO_UPDATE_PERIOD_SECS = 3
47+
48+
#========================== Syslog wrappers ==========================
49+
50+
def log_info(msg, also_print_to_console=False):
51+
syslog.openlog(SYSLOG_IDENTIFIER)
52+
syslog.syslog(syslog.LOG_INFO, msg)
53+
syslog.closelog()
54+
55+
if also_print_to_console:
56+
print msg
57+
58+
def log_warning(msg, also_print_to_console=False):
59+
syslog.openlog(SYSLOG_IDENTIFIER)
60+
syslog.syslog(syslog.LOG_WARNING, msg)
61+
syslog.closelog()
62+
63+
if also_print_to_console:
64+
print msg
65+
66+
def log_error(msg, also_print_to_console=False):
67+
syslog.openlog(SYSLOG_IDENTIFIER)
68+
syslog.syslog(syslog.LOG_ERR, msg)
69+
syslog.closelog()
70+
71+
if also_print_to_console:
72+
print msg
73+
74+
#========================== Signal Handling ==========================
75+
76+
def signal_handler(sig, frame):
77+
if sig == signal.SIGHUP:
78+
log_info("Caught SIGHUP - ignoring...")
79+
return
80+
elif sig == signal.SIGINT:
81+
log_info("Caught SIGINT - exiting...")
82+
sys.exit(128 + sig)
83+
elif sig == signal.SIGTERM:
84+
log_info("Caught SIGTERM - exiting...")
85+
sys.exit(128 + sig)
86+
else:
87+
log_warning("Caught unhandled signal '" + sig + "'")
88+
return
89+
90+
#============ Functions to load platform-specific classes ============
91+
92+
# Returns platform and HW SKU
93+
def get_platform_and_hwsku():
94+
try:
95+
proc = subprocess.Popen([SONIC_CFGGEN_PATH, '-H', '-v', PLATFORM_KEY],
96+
stdout=subprocess.PIPE,
97+
shell=False,
98+
stderr=subprocess.STDOUT)
99+
stdout = proc.communicate()[0]
100+
proc.wait()
101+
platform = stdout.rstrip('\n')
102+
103+
proc = subprocess.Popen([SONIC_CFGGEN_PATH, '-d', '-v', HWSKU_KEY],
104+
stdout=subprocess.PIPE,
105+
shell=False,
106+
stderr=subprocess.STDOUT)
107+
stdout = proc.communicate()[0]
108+
proc.wait()
109+
hwsku = stdout.rstrip('\n')
110+
except OSError, e:
111+
raise OSError("Cannot detect platform")
112+
113+
return (platform, hwsku)
114+
115+
# Loads platform specific psuutil module from source
116+
def load_platform_psuutil():
117+
global platform_psuutil
118+
119+
# Get platform and hwsku
120+
(platform, hwsku) = get_platform_and_hwsku()
121+
122+
# Load platform module from source
123+
platform_path = PLATFORM_ROOT_DOCKER
124+
hwsku_path = "/".join([platform_path, hwsku])
125+
126+
try:
127+
module_file = "/".join([platform_path, "plugins", PLATFORM_SPECIFIC_MODULE_NAME + ".py"])
128+
module = imp.load_source(PLATFORM_SPECIFIC_MODULE_NAME, module_file)
129+
except IOError, e:
130+
log_error("Failed to load platform module '%s': %s" % (PLATFORM_SPECIFIC_MODULE_NAME, str(e)), True)
131+
return -1
132+
133+
try:
134+
platform_psuutil_class = getattr(module, PLATFORM_SPECIFIC_CLASS_NAME)
135+
platform_psuutil = platform_psuutil_class()
136+
except AttributeError, e:
137+
log_error("Failed to instantiate '%s' class: %s" % (PLATFORM_SPECIFIC_CLASS_NAME, str(e)), True)
138+
return -2
139+
140+
return 0
141+
142+
def psu_db_update(psu_tbl, num_psus):
143+
for psu_index in range(1, num_psus + 1):
144+
fvs = swsscommon.FieldValuePairs([('presence',
145+
'true' if platform_psuutil.get_psu_presence(psu_index) else 'false'),
146+
('status',
147+
'true' if platform_psuutil.get_psu_status(psu_index) else 'false')])
148+
psu_tbl.set("PSU {}".format(psu_index), fvs)
149+
150+
#=============================== Main ================================
151+
152+
def main():
153+
log_info("Starting up...")
154+
155+
# Register our signal handlers
156+
signal.signal(signal.SIGHUP, signal_handler)
157+
signal.signal(signal.SIGINT, signal_handler)
158+
signal.signal(signal.SIGTERM, signal_handler)
159+
160+
# Load platform-specific psuutil class
161+
err = load_platform_psuutil()
162+
if err != 0:
163+
log_error("failed to load psuutil")
164+
sys.exit(1)
165+
166+
state_db = swsscommon.DBConnector(swsscommon.STATE_DB,
167+
REDIS_HOSTNAME,
168+
REDIS_PORT,
169+
REDIS_TIMEOUT_MSECS)
170+
psu_tbl = swsscommon.Table(state_db, "PSU_INFO")
171+
chassis_tbl = swsscommon.Table(state_db, "CHASSIS_INFO")
172+
num_psus = platform_psuutil.get_num_psus()
173+
fvs = swsscommon.FieldValuePairs([('num_psus', str(num_psus))])
174+
chassis_tbl.set('chassis 1', fvs)
175+
176+
# Start main loop to listen to the PSU change event.
177+
log_info("Start main loop")
178+
while True:
179+
psu_db_update(psu_tbl, num_psus)
180+
time.sleep(PSU_INFO_UPDATE_PERIOD_SECS)
181+
182+
# Clean all the information from DB and then exit
183+
for psu_index in range(1, num_psus + 1):
184+
psu_tbl._del("PSU {}".format(psu_index))
185+
chassis_tbl._del('chassis 1')
186+
log_error("Error: return error from psu daemon, exiting...")
187+
return 1
188+
189+
if __name__ == '__main__':
190+
main()

sonic-psud/setup.py

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from setuptools import setup
2+
3+
setup(
4+
name='sonic-psud',
5+
version='1.0',
6+
description='PSU daemon for SONiC',
7+
license='Apache 2.0',
8+
author='SONiC Team',
9+
author_email='[email protected]',
10+
url='https://github.com/Azure/sonic-platform-daemons',
11+
maintainer='Kevin Wang',
12+
maintainer_email='[email protected]',
13+
scripts=[
14+
'scripts/psud',
15+
],
16+
classifiers=[
17+
'Development Status :: 4 - Beta',
18+
'Environment :: No Input/Output (Daemon)',
19+
'Intended Audience :: Developers',
20+
'Intended Audience :: Information Technology',
21+
'Intended Audience :: System Administrators',
22+
'License :: OSI Approved :: Apache Software License',
23+
'Natural Language :: English',
24+
'Operating System :: POSIX :: Linux',
25+
'Programming Language :: Python :: 2.7',
26+
'Topic :: System :: Hardware',
27+
],
28+
keywords='sonic SONiC psu PSU daemon psud PSUD',
29+
)

0 commit comments

Comments
 (0)