tests: Add NVMe-MI recovery test

Exercise the NVMeMi endpoint recovery implementation by exploiting the
link seam for libnvme{,-mi}.

To do this we first reuse the headers provided by libnvme with a
partial-dependency declaration in the test. From there we provide our
own (largely neutered) implementation of the APIs to link against.

With this in-place we can trivially recreate the error discussed in[1]
by returning erroneous values from the libnvme APIs. The higher-level
consequence is we drive the NVMeMi implementation through the endpoint
recovery process while trying to optimize the connection. At that point
we can use the usual GMock APIs to define the behaviors we want from
MctpDevice and MctpEndpoint during the recovery process.

[1]: https://github.com/CodeConstruct/dbus-sensors/issues/14

An interesting future direction is to allow mocks to be injected behind
the libnvme{,-mi} link seam. Adding a method to inject mocks enables
describing arbitrary NVMe scenarios in the usual gmock style, which has
the potential to drastically increase test code coverage for nvmesensor.

Returning to the change at hand, note that the test case causes valgrind
to report a leak, however this appears to be from the gmock error report
itself. Given we're working to resolve the gmock error, the leak will
disappear with it:

```
...
Mock MCTP Device] MCTP connection is not established
error reading ctemp from subsystem, reason:Transport endpoint is not connected
Sensor bar reading error!
../tests/test_nvme_mi_recovery.cpp:63: Failure
Mock function called more times than expected - returning directly.
    Function call: recover()
         Expected: to be called between 1 and 3 times
           Actual: called 5 times - over-saturated and active

[Mock MCTP Device] MCTP connection is not established
error reading ctemp from subsystem, reason:Transport endpoint is not connected
../tests/test_nvme_mi_recovery.cpp:63: Failure
Mock function called more times than expected - returning directly.
    Function call: recover()
         Expected: to be called between 1 and 3 times
           Actual: called 6 times - over-saturated and active

status else
poll loop has been canceled
../tests/test_nvme_mi_recovery.cpp:90: Failure
Value of: testing::Mock::VerifyAndClearExpectations(mctpEp.get())
  Actual: false
Expected: true

[  FAILED  ] NVMeRecovery.optimisationFailure (8348 ms)
[----------] 1 test from NVMeRecovery (8359 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (8415 ms total)
[  PASSED  ] 0 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] NVMeRecovery.optimisationFailure

 1 FAILED TEST
==197574==
==197574== HEAP SUMMARY:
==197574==     in use at exit: 8 bytes in 1 blocks
==197574==   total heap usage: 7,500 allocs, 7,499 frees, 1,751,392 bytes allocated
==197574==
==197574== 8 bytes in 1 blocks are still reachable in loss record 1 of 1
==197574==    at 0x4840F83: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==197574==    by 0xD157EF: testing::internal::GetFailureReporter() (gmock-internal-utils.cc:124)
==197574==    by 0x9C16E3: testing::internal::Expect(bool, char const*, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (gmock-internal-utils.h:259)
==197574==    by 0x9C2303: testing::internal::UntypedFunctionMockerBase::FailureCleanupHandler::~FailureCleanupHandler() (gmock-spec-builders.h:1427)
==197574==    by 0x9D852C: testing::internal::FunctionMocker<void ()>::InvokeWith(std::tuple<>&&) (gmock-spec-builders.h:1898)
==197574==    by 0x9CB22B: testing::internal::FunctionMocker<void ()>::Invoke() (gmock-spec-builders.h:1548)
==197574==    by 0x9C2EF8: MockMctpEndpoint::recover() (test_nvme_mi_recovery.cpp:27)
==197574==    by 0xAD57EB: NVMeMi::recover() (NVMeMi.cpp:192)
==197574==    by 0xAD50B1: NVMeMi::epOptimize()::{lambda(boost::system::error_code)#1}::operator()(boost::system::error_code) const::{lambda(std::error_code const&)#1}::operator()(std::error_code const&) const (NVMeMi.cpp:167)
==197574==    by 0xAFF26F: void std::__invoke_impl<void, NVMeMi::epOptimize()::{lambda(boost::system::error_code)#1}::operator()(boost::system::error_code) const::{lambda(std::error_code const&)#1}&, std::error_code const&>(std::__invoke_other, NVMeMi::epOptimize()::{lambda(boost::system::error_code)#1}::operator()(boost::system::error_code) const::{lambda(std::error_code const&)#1}&, std::error_code const&) (invoke.h:61)
==197574==    by 0xAF3ED2: std::enable_if<is_invocable_r_v<void, NVMeMi::epOptimize()::{lambda(boost::system::error_code)#1}::operator()(boost::system::error_code) const::{lambda(std::error_code const&)#1}&, std::error_code const&>, void>::type std::__invoke_r<void, NVMeMi::epOptimize()::{lambda(boost::system::error_code)#1}::operator()(boost::system::error_code) const::{lambda(std::error_code const&)#1}&, std::error_code const&>(NVMeMi::epOptimize()::{lambda(boost::system::error_code)#1}::operator()(boost::system::error_code) const::{lambda(std::error_code const&)#1}&, std::error_code const&) (invoke.h:111)
==197574==    by 0xAEFE8F: std::_Function_handler<void (std::error_code const&), NVMeMi::epOptimize()::{lambda(boost::system::error_code)#1}::operator()(boost::system::error_code) const::{lambda(std::error_code const&)#1}>::_M_invoke(std::_Any_data const&, std::error_code const&) (std_function.h:290)
==197574==
==197574== LEAK SUMMARY:
==197574==    definitely lost: 0 bytes in 0 blocks
==197574==    indirectly lost: 0 bytes in 0 blocks
==197574==      possibly lost: 0 bytes in 0 blocks
==197574==    still reachable: 8 bytes in 1 blocks
==197574==         suppressed: 0 bytes in 0 blocks
==197574==
==197574== For lists of detected and suppressed errors, rerun with: -s
==197574== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
```

Change-Id: Idf3fb6d167a9e11ea1d554c4ab2f2c50d1489421
Signed-off-by: Andrew Jeffery <andrew@codeconstruct.com.au>
4 files changed
tree: e3e61224b9f8dd9122392111ef220a26c3e0b430
  1. gen/
  2. include/
  3. proto/
  4. service_files/
  5. src/
  6. subprojects/
  7. tests/
  8. yaml/
  9. .clang-format
  10. .clang-tidy
  11. .clang-tidy-ignore
  12. .gitignore
  13. build.md
  14. gemini.md
  15. lesson.md
  16. LICENSE
  17. meson.build
  18. meson_options.txt
  19. OWNERS
  20. README.md
README.md

dbus-sensors

dbus-sensors is a collection of sensor applications that provide the xyz.openbmc_project.Sensor collection of interfaces. They read sensor values from hwmon, d-bus, or direct driver access to provide readings. Some advance non-sensor features such as fan presence, pwm control, and automatic cpu detection (x86) are also supported.

key features

  • runtime re-configurable from d-bus (entity-manager or the like)

  • isolated: each sensor type is isolated into its own daemon, so a bug in one sensor is unlikely to affect another, and single sensor modifications are possible

  • async single-threaded: uses sdbusplus/asio bindings

  • multiple data inputs: hwmon, d-bus, direct driver access

dbus interfaces

A typical dbus-sensors object support the following dbus interfaces:

Path        /xyz/openbmc_project/sensors/<type>/<sensor_name>

Interfaces  xyz.openbmc_project.Sensor.Value
            xyz.openbmc_project.Sensor.Threshold.Critical
            xyz.openbmc_project.Sensor.Threshold.Warning
            xyz.openbmc_project.State.Decorator.Availability
            xyz.openbmc_project.State.Decorator.OperationalStatus
            xyz.openbmc_project.Association.Definitions

Sensor interfaces collection are described here.

Consumer examples of these interfaces are Redfish, Phosphor-Pid-Control, IPMI SDR.

Reactor

dbus-sensor daemons are reactors that dynamically create and update sensors configuration when system configuration gets updated.

Using asio timers and async calls, dbus-sensor daemons read sensor values and check thresholds periodically. PropertiesChanged signals will be broadcasted for other services to consume when value or threshold status change. OperationStatus is set to false if the sensor is determined to be faulty.

A simple sensor example can be found here.

configuration

Sensor devices are described using Exposes records in configuration file. Name and Type fields are required. Different sensor types have different fields. Refer to entity manager schema for complete list.

sensor documentation