|
| 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() |
0 commit comments