Source code for testcases.DeviceTreeValidation

#!/usr/bin/env python3
# OpenPOWER Automated Test Project
#
# Contributors Listed Below - COPYRIGHT 2017
# [+] International Business Machines Corp.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied. See the License for the specific language governing
# permissions and limitations under the License.
#
# DeviceTreeValidation
# In this testcase we can validate different DT nodes and properties
# of different OPAL subcomponents. And also we can validate the DT
# of skiroot against host.
#

'''
DeviceTreeValidation
--------------------

Check a bunch of device tree properties and structure for validity,
and compare device tree in host and skiroot environments.
'''

import unittest
import re
import struct
import difflib

import OpTestConfiguration
from common.Exceptions import CommandFailed
from common.OpTestError import OpTestError
from common.OpTestSystem import OpSystemState
from common.OpTestConstants import OpTestConstants as BMC_CONST
import common.OpTestQemu as OpTestQemu
import logging
import OpTestLogger
log = OpTestLogger.optest_logger_glob.get_logger(__name__)

MAX_PSTATES = 256
CPUIDLE_STATE_MAX = 10

# We use some globals to keep state between IPLs
# Which means we can save an IPL in running full test suite
prop_val_pair_skiroot = {}
prop_val_pair_host = {}


[docs]class DeviceTreeValidation(unittest.TestCase): DO_FULL_TEST = 0
[docs] def setUp(self): conf = OpTestConfiguration.conf self.cv_HOST = conf.host() self.cv_IPMI = conf.ipmi() self.cv_SYSTEM = conf.system() self.bmc_type = conf.args.bmc_type self.node = "/proc/device-tree/ibm,opal/"
# Checks for monotonocity/strictly increase/decrease of values def strictly_increasing(self, L): return all(x < y for x, y in zip(L, L[1:])) def strictly_decreasing(self, L): return all(x > y for x, y in zip(L, L[1:])) def non_increasing(self, L): return all(x >= y for x, y in zip(L, L[1:])) def non_decreasing(self, L): return all(x <= y for x, y in zip(L, L[1:])) # two's complement of integers
[docs] def twos_comp(self, val, bits): """compute the 2's complement of int value val""" # if sign bit is set e.g., 8bit: 128-255 if (val & (1 << (bits - 1))) != 0: # compute negative value val = val - (1 << bits) # return positive value as is return val
# 32 bit BE to LE Conversion def swap32(self, i): return struct.unpack("<I", struct.pack(">I", i))[0] # 64 bit BE to LE Conversion def swap64(self, i): return struct.unpack("<Q", struct.pack(">Q", i))[0] def dt_prop_read_str_arr(self, prop): res = self.c.run_command("lsprop /proc/device-tree/%s" % prop) if "bytes total" in "".join(res): res = res[1:-1] else: res = res[1:] list = [] for line in res: line = re.sub("\(.*\)", "", line) list = list + line.strip("\t\r\n ").split(" ") return list def dt_prop_read_u32_arr(self, prop): res = self.c.run_command("hexdump -v -e \'1/4 \"%%08x\" \"\\n\"\' " "/proc/device-tree/%s" % prop) list = [] for line in res: val = ("{:08x}".format(self.swap32(int(line, 16)))) list.append(val) return list def dt_prop_read_u64_arr(self, prop): res = self.c.run_command("hexdump -v -e \'2/4 \"%%08x\" \"\\n\"\' " "/proc/device-tree/%s" % prop) list = [] for line in res: val = ("{:016x}".format(self.swap64(int(line, 16)))) list.append(val) return list def validate_idle_state_properties(self): idle_state_names = self.dt_prop_read_str_arr( "ibm,opal/power-mgt/ibm,cpu-idle-state-names") idle_state_flags = self.dt_prop_read_u32_arr( "ibm,opal/power-mgt/ibm,cpu-idle-state-flags") idle_state_latencies_ns = self.dt_prop_read_u32_arr( "ibm,opal/power-mgt/ibm,cpu-idle-state-latencies-ns") idle_state_residency_ns = self.dt_prop_read_u32_arr( "ibm,opal/power-mgt/ibm,cpu-idle-state-residency-ns") if self.cv_HOST.host_get_proc_gen(console=1) in ["POWER8", "POWER8E"]: has_stop_inst = False control_prop = "ibm,opal/power-mgt/ibm,cpu-idle-state-pmicr" mask_prop = "ibm,opal/power-mgt/ibm,cpu-idle-state-pmicr-mask" elif self.cv_HOST.host_get_proc_gen(console=1) in ["POWER9", "POWER9P"]: has_stop_inst = True control_prop = "ibm,opal/power-mgt/ibm,cpu-idle-state-psscr" mask_prop = "ibm,opal/power-mgt/ibm,cpu-idle-state-psscr-mask" idle_states_control_array = self.dt_prop_read_u64_arr(control_prop) idle_states_mask_array = self.dt_prop_read_u64_arr(mask_prop) log.debug("\n \ List of idle states: %s\n \ Idle state flags: %s\n \ Idle state latencies ns: %s\n \ Idle state residency ns: %s\n \ Idle state control property: %s\n \ Idle state mask property: %s\n " % (idle_state_names, idle_state_flags, idle_state_latencies_ns, idle_state_residency_ns, idle_states_control_array, idle_states_mask_array)) # Validate ibm,cpu-idle-state-flags property self.assertGreater(len(idle_state_flags), 0, "No idle states found in DT") self.assertGreater(CPUIDLE_STATE_MAX, len(idle_state_flags), "More idle states found in DT than the expected") # Validate names, latencies and residency properties self.assertEqual(len(idle_state_flags), len(idle_state_names), "Array size mismatch") self.assertEqual(len(idle_state_flags), len(idle_state_latencies_ns), "Array size mismatch") self.assertEqual(len(idle_state_flags), len(idle_state_residency_ns), "Array size mismatch") self.assertEqual(len(idle_state_flags), len(idle_states_control_array), "Array size mismatch") self.assertEqual(len(idle_state_flags), len(idle_states_mask_array), "Array size mismatch") # Validate residency and latency counters are in increasing order self.assertTrue(self.strictly_increasing(idle_state_residency_ns), "Non monotonicity observed for residency values") self.assertTrue(self.strictly_increasing(idle_state_latencies_ns), "Non monotonicity observed for latency values") self.non_decreasing(idle_state_residency_ns) self.non_decreasing(idle_state_latencies_ns) def validate_pstate_properties(self): pstate_ids = self.dt_prop_read_u32_arr( "ibm,opal/power-mgt/ibm,pstate-ids") pstate_min = self.dt_prop_read_u32_arr( "ibm,opal/power-mgt/ibm,pstate-min") pstate_max = self.dt_prop_read_u32_arr( "ibm,opal/power-mgt/ibm,pstate-max") pstate_nominal = self.dt_prop_read_u32_arr( "ibm,opal/power-mgt/ibm,pstate-nominal") pstate_turbo = self.dt_prop_read_u32_arr( "ibm,opal/power-mgt/ibm,pstate-turbo") pstate_frequencies = self.dt_prop_read_u32_arr( "ibm,opal/power-mgt/ibm,pstate-frequencies-mhz") nr_pstates = abs(self.twos_comp(int(pstate_max[0], 16), 32) - self.twos_comp(int(pstate_min[0], 16), 32)) + 1 log.debug("\n \ List of pstate_ids: {}\n \ Minimum pstate: {}\n \ Maximum pstate: {}\n \ Nominal pstate: {}\n \ Turbo pstate: {}\n \ Pstate frequencies: {}\n \ Number of pstates: {}\n".format(pstate_ids, pstate_min, pstate_max, pstate_nominal, pstate_turbo, pstate_frequencies, nr_pstates)) if (nr_pstates <= 1 or nr_pstates > 128): if self.cv_HOST.host_get_proc_gen(console=1) in ["POWER8", "POWER8E"]: self.assertTrue(False, "pstates range {} is not valid".format( nr_pstates)) elif self.cv_HOST.host_get_proc_gen(console=1) in ["POWER9", "POWER9P"]: self.assertTrue(False, "More than 128 pstates found {}" "in pstate table".format(nr_pstates)) self.assertEqual(nr_pstates, len(pstate_ids), "Wrong number of pstates, " "Expected %s, found %s".format(nr_pstates, len(pstate_ids))) if self.cv_HOST.host_get_proc_gen(console=1) in ["POWER8", "POWER8E"]: id_list = [] for id in pstate_ids: id_list.append(self.twos_comp(int(id, 16), 32)) self.assertTrue(self.strictly_decreasing(id_list), "Non monotonocity observed for pstate ids") elif self.cv_HOST.host_get_proc_gen(console=1) in ["POWER9", "POWER9P"]: self.assertTrue(self.strictly_increasing(pstate_ids), "Non monotonocity observed for pstate ids") def validate_firmware_version(self): fw_node = "/proc/device-tree/ibm,firmware-versions/" # Validate firmware version properties if self.bmc_type not in ['OpenBMC', 'SMC', 'AMI']: self.skipTest( "ibm,firmware-versions DT node not available on this system") if self.cv_HOST.host_get_proc_gen() not in ["POWER8", "POWER8E"]: try: self.c.run_command("ls --color=never %s/version" % fw_node) version = self.dt_prop_read_str_arr( "ibm,firmware-versions/version") if not version: raise OpTestError("DT: Firmware version property is empty") except CommandFailed: raise OpTestError("DT: Firmware version property is missing") props = self.c.run_command("find %s -type f" % fw_node) for prop in props: val = self.c.run_command("lsprop %s" % prop) if not val: raise OpTestError( "DT: Firmware component (%s) is empty" % prop) def check_dt_matches(self): # allows the ability to filter for debug # skip the parent(s) in the hierarchy # leave as None to get checked # setting to anything other than None will ignore ignore_dict = {"/proc/device-tree/ibm,opal/": None, "/proc/device-tree/ibm,opal/sensor-groups": None, "/proc/device-tree/ibm,opal/sensors": None, "/proc/device-tree/ibm,opal/power-mgt": None, "/proc/device-tree/ibm,opal/fw-features": None, } if len(prop_val_pair_skiroot) and len(prop_val_pair_host): unified_failed_props = missing_prop_in_skiroot = \ missing_prop_in_host = matched_prop_in_host = 0 host_props = unified_diff_passed = \ skipped_props = skiroot_props = 0 unified_failures = [] missing_in_host = [] missing_in_skiroot = [] matched_in_host = [] skiroot_check = all(elem in prop_val_pair_host for elem in prop_val_pair_skiroot) host_check = all(elem in prop_val_pair_skiroot for elem in prop_val_pair_host) for prop in prop_val_pair_host: host_props += 1 if prop not in prop_val_pair_skiroot: # these failures will show up in the host_check # log the specifics missing_prop_in_skiroot += 1 missing_in_skiroot.append(["DT Node \"{}\" found in host" " but does NOT exist in skiroot".format(prop)]) log.debug("DT Node \"{}\" found in host" " but does NOT exist in skiroot".format(prop)) for prop in prop_val_pair_skiroot: skiroot_props += 1 if prop in prop_val_pair_host: check1_len = len(prop_val_pair_skiroot[prop]) check2_len = len(prop_val_pair_host[prop]) # we need the debug to figure out why things fail for elem in prop_val_pair_skiroot[prop]: log.debug("skiroot elem={}".format(elem)) for elem in prop_val_pair_host[prop]: log.debug("host elem={}".format(elem)) check1_skiroot = all(elem in prop_val_pair_host[prop] for elem in prop_val_pair_skiroot[prop]) check2_skiroot = all(elem in prop_val_pair_skiroot[prop] for elem in prop_val_pair_host[prop]) # if cross check good and same length (just in case # duplicate list items) trying to catch any mods) # if order of list content differs, the unified diff # always flags as difference even though # the elements match (just in a different order) if check1_skiroot and check2_skiroot \ and (check1_len == check2_len): matched_prop_in_host += 1 matched_in_host.append(["Skiroot DT Node \"{}\" found" " in skiroot and exists in host".format(prop)]) log.debug("DT Node \"{}\" is the same in both" " skiroot and host".format(prop)) continue log.debug("Skiroot DT Node \"{}\" appears " "to differ from the Host, ^^^ compare the debug output ^^^" .format(prop)) log.debug("skiroot_len={} host_len={}" .format(check1_len, check2_len)) log.debug("skiroot_cross_check_to_host={} host_cross_check_to_skiroot={}" .format(check1_skiroot, check2_skiroot)) if ignore_dict.get(str(prop)) != None: skipped_props += 1 continue unified_output = difflib.unified_diff( [_f for _f in prop_val_pair_skiroot[prop] if _f], [_f for _f in prop_val_pair_host[prop] if _f], fromfile="skiroot", tofile="host", lineterm="") unified_list = list(unified_output) if len(unified_list): unified_failed_props += 1 unified_failures.append(["<----------> Diff Failure #{}: \"{}\"" .format(unified_failed_props, prop)]) unified_failures.append(unified_list) else: # diff is OK, should not get here # good cases should have been filtered above unified_diff_passed += 1 log.debug("Unexpected case unified_diff passed={}" .format(prop)) else: # these failures will show up in the skiroot_check # log the specifics missing_prop_in_host += 1 missing_in_host.append(["DT Node \"{}\" does not" " exist in the host OS".format(prop)]) if unified_failed_props \ or (skiroot_props != host_props) \ or not skiroot_check \ or not host_check: for i in unified_failures: line_num = 1 for j in i: log.debug("Failure Line #{} {}".format(line_num, "".join(j))) line_num += 1 self.assertTrue(False, "DT Diff failures detected Total={}" "\nSUMMARY:\n{}\n" "Missing (found in skiroot not in host):\n{}\n" "Missing (found in host not in skiroot):\n{}\n" "Skipped Nodes (usually 0 or 1, the parent) = {}\n" "Cross Check matched (skiroot to host) = {}\n" "Skiroot Nodes = {}\n" "Host Nodes = {}\n" "Skiroot Nodes matched host Nodes = {}\n" "Host Nodes matched skiroot Nodes = {}\n" "Failures:{}\n" .format( unified_failed_props, ('\n'.join(i for f in unified_failures for i in f)), (None if len(missing_in_host) == 0 else ('\n'.join(i for f in missing_in_host for i in f))), (None if len(missing_in_skiroot) == 0 else ('\n'.join(i for f in missing_in_skiroot for i in f))), skipped_props, matched_prop_in_host, skiroot_props, host_props, skiroot_check, host_check, unified_failed_props, )) else: log.debug("DT Diff success") def runTest(self): self.skipTest("Not meant to be run directly. " "Run skiroot/host variants")
[docs]class DeviceTreeValidationSkiroot(DeviceTreeValidation): def runTest(self): # goto PS before running any commands self.cv_SYSTEM.goto_state(OpSystemState.PETITBOOT_SHELL) if self.cv_HOST.host_get_proc_gen(console=1) not in ["POWER8", "POWER8E", "POWER9", "POWER9P"]: self.skipTest("Unknown CPU type {}".format( self.cv_HOST.host_get_proc_gen(console=1))) system_state = self.cv_SYSTEM.get_state() self.c = self.cv_SYSTEM.console if isinstance(self.c, OpTestQemu.QemuConsole): raise self.skipTest("OpTestSystem running QEMU so comparing " "Skiroot to Host is not applicable") self.cv_HOST.host_get_proc_gen(console=1) self.validate_idle_state_properties() self.validate_pstate_properties() self.validate_firmware_version() # Validate ibm,opal node DT content at skiroot against host # We can extend for other nodes as well, which are suspicieous. props = self.c.run_command("find %s -type d" % self.node) for prop in props: # Not all distros consistently output lsprop (-R) so make it consistent # Otherwise we get mismatches which get flagged as failures # https://github.com/ibm-power-utilities/powerpc-utils prop_val_pair_skiroot[prop] = self.c.run_command( "lsprop -R %s" % prop) self.check_dt_matches()
[docs]class DeviceTreeValidationHost(DeviceTreeValidation): def runTest(self): # goto OS before running any commands self.cv_SYSTEM.goto_state(OpSystemState.OS) if self.cv_HOST.host_get_proc_gen(console=1) not in ["POWER8", "POWER8E", "POWER9", "POWER9P"]: self.skipTest("Unknown CPU type {}".format( self.cv_HOST.host_get_proc_gen(console=1))) self.c = self.cv_SYSTEM.cv_HOST.get_ssh_connection() self.cv_HOST.host_get_proc_gen(console=1) self.validate_idle_state_properties() self.validate_pstate_properties() self.validate_firmware_version() props = self.c.run_command("find %s -type d" % self.node) for prop in props: # Not all distros consistently output lsprop (-R) so make it consistent # Otherwise we get mismatches which get flagged as failures # https://github.com/ibm-power-utilities/powerpc-utils prop_val_pair_host[prop] = self.c.run_command( "lsprop -R %s" % prop) self.check_dt_matches()