Repository: kontron/python-ipmi

Issue:
device_sdr_entries fails on ipmitool `Received a response with unexpected ID 0 vs. 1`  
I'm seeing intermittent stack traces when reading sensor values:
```
  File "//test.py", line 21, in <module>
    for sensor in iter_func():
  File "/root/.pyenv/versions/3.11.8/lib/python3.11/site-packages/pyipmi/sensor.py", line 149, in device_sdr_entries
    record = self.get_device_sdr(record_id, reservation_id)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/.pyenv/versions/3.11.8/lib/python3.11/site-packages/pyipmi/sensor.py", line 133, in get_device_sdr
    get_sdr_data_helper(self.reserve_device_sdr_repository,
  File "/root/.pyenv/versions/3.11.8/lib/python3.11/site-packages/pyipmi/helper.py", line 85, in get_sdr_data_helper
    (next_id, data) = get_fn(reservation_id, record_id, offset, length)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/.pyenv/versions/3.11.8/lib/python3.11/site-packages/pyipmi/sensor.py", line 120, in _get_device_sdr_chunk
    rsp = get_sdr_chunk_helper(self.send_message, req,
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/.pyenv/versions/3.11.8/lib/python3.11/site-packages/pyipmi/helper.py", line 30, in get_sdr_chunk_helper
    rsp = send_fn(req)
          ^^^^^^^^^^^^
  File "/root/.pyenv/versions/3.11.8/lib/python3.11/site-packages/pyipmi/__init__.py", line 196, in send_message
    rsp = self.interface.send_and_receive(req)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/.pyenv/versions/3.11.8/lib/python3.11/site-packages/pyipmi/interfaces/ipmitool.py", line 179, in send_and_receive
    rsp_data = self.send_and_receive_raw(req.target, req.lun, req.netfn,
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/.pyenv/versions/3.11.8/lib/python3.11/site-packages/pyipmi/interfaces/ipmitool.py", line 154, in send_and_receive_raw
    cc, rsp = self._parse_output(output)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/.pyenv/versions/3.11.8/lib/python3.11/site-packages/pyipmi/interfaces/ipmitool.py", line 135, in _parse_output
    rsp = array('B', [
                     ^
  File "/root/.pyenv/versions/3.11.8/lib/python3.11/site-packages/pyipmi/interfaces/ipmitool.py", line 136, in <listcomp>
    int(value, 16) for value in hexstr.split(' ')
    ^^^^^^^^^^^^^^
ValueError: invalid literal for int() with base 16: 'Received'
```

I added a debug print near the failure:
```
        if len(hexstr):
+            log().debug(f'{hexstr=}')
            rsp = array('B', [
                int(value, 16) for value in hexstr.split(' ')
            ])
```

The output on failure:
```
...
System Temp : 35
hexstr=96 01 53 01 51 01 3a
hexstr=96 01 20 00 0c 07 03 7d 68 01 01 04 2a 04 6a 3f 3f 00 01 00 00 01
hexstr=96 01 00 00 00 00 00 07 14 4f 0b ff 00 5a 55 50 05 05 0a 02 02 00
hexstr=96 01 00 00 cf 50 65 72 69 70 68 65 72 61 6c 20 54 65 6d 70
hexstr=1e c0 c0
Peripheral Temp : 30
hexstr=d9 01 96 01 51 01 3a
hexstr=d9 01 20 00 10 07 10 7d 68 01 01 00 0a 00 6a 3f 3f 00 01 00 00 01
hexstr=d9 01 00 00 00 00 00 07 14 5e 0b ff 00 69 64 5f 05 05 0a 02 02 00
hexstr=Received a response with unexpected ID 0 vs. 1 d9 01 00 00 cf 43 50 55 31 5f 56 52 4d 49 4e 20 54 65 6d 70
```

Appears to be related to this issue: https://github.com/ipmitool/ipmitool/issues/399

I think it would be sensible to match and handle this output equivalent to the 'failure' lines.

Hints from issue comments:
@hthiery: I just merged the mentioned PR. Please retry.

Solution patch:
diff --git a/pyipmi/interfaces/ipmitool.py b/pyipmi/interfaces/ipmitool.py
index 815041a..d0adb2e 100644
--- a/pyipmi/interfaces/ipmitool.py
+++ b/pyipmi/interfaces/ipmitool.py
@@ -117,6 +117,9 @@ class Ipmitool(object):
             # Don't try to parse ipmitool error messages
             if 'failed' in line:
                 continue
+            # Don't try to parse spurious ipmitool output
+            if 'Received a response with unexpected ID' in line:
+                continue
 
             # Check for timeout
             if self.re_timeout.match(line):


Test patch:
diff --git a/tests/interfaces/test_ipmitool.py b/tests/interfaces/test_ipmitool.py
index 0d4f860..4ae59bd 100644
--- a/tests/interfaces/test_ipmitool.py
+++ b/tests/interfaces/test_ipmitool.py
@@ -192,6 +192,13 @@ class TestIpmitool:
         assert cc is None
         assert py3_array_tobytes(rsp) == b'\x12\x34\x56\x78\xd0\x0f\xaf\xfe\xde\xad\xbe\xef\xaa\x55\xbb'
 
+    def test_parse_output_rsp_suppressed_unexpected_id(self):
+        test_str = b'Received a response with unexpected ID 0 vs. 1\n'\
+                   b' 12 34 56 78 \r\n d0 0f af fe de ad be ef\naa 55\r\nbb    \n'
+        cc, rsp = self._interface._parse_output(test_str)
+        assert cc is None
+        assert py3_array_tobytes(rsp) == b'\x12\x34\x56\x78\xd0\x0f\xaf\xfe\xde\xad\xbe\xef\xaa\x55\xbb'
+
     def test_parse_output_cc(self):
         test_str = b'Unable to send RAW command (channel=0x0 netfn=0x6 lun=0x0 cmd=0x1 rsp=0xcc): Ignore Me\n'
         cc, rsp = self._interface._parse_output(test_str)