python-ipmi 0.5.8__py3-none-any.whl → 0.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. pyipmi/__init__.py +269 -86
  2. pyipmi/bmc.py +198 -29
  3. pyipmi/chassis.py +205 -61
  4. pyipmi/constants.py +35 -0
  5. pyipmi/dcmi.py +356 -19
  6. pyipmi/errors.py +34 -22
  7. pyipmi/event.py +62 -3
  8. pyipmi/fields.py +41 -30
  9. pyipmi/fru.py +1107 -122
  10. pyipmi/helper.py +50 -16
  11. pyipmi/hpm.py +932 -203
  12. pyipmi/interfaces/__init__.py +77 -4
  13. pyipmi/interfaces/aardvark.py +170 -167
  14. pyipmi/interfaces/base.py +179 -0
  15. pyipmi/interfaces/ipmb.py +438 -86
  16. pyipmi/interfaces/ipmbdev.py +71 -158
  17. pyipmi/interfaces/ipmidev.py +324 -0
  18. pyipmi/interfaces/ipmitool.py +258 -90
  19. pyipmi/interfaces/mock.py +38 -16
  20. pyipmi/interfaces/openipmblink.py +423 -0
  21. pyipmi/interfaces/rmcp.py +499 -213
  22. pyipmi/interfaces/rmcpplus.py +735 -0
  23. pyipmi/interfaces/router.py +319 -0
  24. pyipmi/ipmitool.py +1120 -429
  25. pyipmi/lan.py +251 -76
  26. pyipmi/logger.py +4 -4
  27. pyipmi/messaging.py +207 -18
  28. pyipmi/mixin.py +52 -0
  29. pyipmi/msgs/__init__.py +0 -1
  30. pyipmi/msgs/constants.py +98 -0
  31. pyipmi/msgs/dcmi.py +177 -19
  32. pyipmi/msgs/device_messaging.py +34 -0
  33. pyipmi/msgs/fru.py +3 -1
  34. pyipmi/msgs/hpm.py +7 -7
  35. pyipmi/msgs/message.py +124 -84
  36. pyipmi/msgs/picmg.py +7 -5
  37. pyipmi/msgs/registry.py +18 -9
  38. pyipmi/msgs/sel.py +4 -4
  39. pyipmi/msgs/sensor.py +19 -0
  40. pyipmi/msgs/vita.py +0 -1
  41. pyipmi/picmg.py +523 -85
  42. pyipmi/sdr.py +598 -105
  43. pyipmi/sel.py +193 -28
  44. pyipmi/sensor.py +186 -48
  45. pyipmi/session.py +58 -34
  46. pyipmi/state.py +13 -5
  47. pyipmi/utils.py +64 -56
  48. pyipmi/version.py +1 -1
  49. pyipmi/vita.py +281 -0
  50. python_ipmi-0.6.1.dist-info/METADATA +372 -0
  51. python_ipmi-0.6.1.dist-info/RECORD +62 -0
  52. {python_ipmi-0.5.8.dist-info → python_ipmi-0.6.1.dist-info}/WHEEL +1 -1
  53. pyipmi/emulation.py +0 -395
  54. python_ipmi-0.5.8.dist-info/METADATA +0 -207
  55. python_ipmi-0.5.8.dist-info/RECORD +0 -56
  56. {python_ipmi-0.5.8.dist-info → python_ipmi-0.6.1.dist-info}/entry_points.txt +0 -0
  57. {python_ipmi-0.5.8.dist-info → python_ipmi-0.6.1.dist-info}/licenses/AUTHORS +0 -0
  58. {python_ipmi-0.5.8.dist-info → python_ipmi-0.6.1.dist-info}/licenses/COPYING +0 -0
  59. {python_ipmi-0.5.8.dist-info → python_ipmi-0.6.1.dist-info}/top_level.txt +0 -0
pyipmi/__init__.py CHANGED
@@ -1,4 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
1
  # Copyright (c) 2014 Kontron Europe GmbH
3
2
  #
4
3
  # This library is free software; you can redistribute it and/or
@@ -15,10 +14,39 @@
15
14
  # License along with this library; if not, write to the Free Software
16
15
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
16
 
18
- from __future__ import absolute_import
17
+ """A pure Python IPMI library.
18
+
19
+ A connection to an IPMI device is an :class:`Ipmi` object, created by
20
+ :func:`create_connection` for an interface (see
21
+ :func:`pyipmi.interfaces.create_interface`).
22
+ The connection sends the requests to its :class:`Target`, the BMC or a
23
+ controller behind it, which is reached over the :class:`Routing` hops of
24
+ the target. The IPMI commands are the methods of :class:`Ipmi`, which
25
+ inherits them from the command groups of the modules, e.g.
26
+ :mod:`pyipmi.bmc` and :mod:`pyipmi.sdr`.
27
+
28
+ Example:
29
+ Print the device ID of a BMC over RMCP+::
30
+
31
+ import pyipmi
32
+ import pyipmi.interfaces
33
+
34
+ interface = pyipmi.interfaces.create_interface('rmcpplus')
35
+ ipmi = pyipmi.create_connection(interface)
36
+ ipmi.session.set_session_type_rmcp('10.0.0.1', port=623)
37
+ ipmi.session.set_auth_type_user('admin', 'admin')
38
+ ipmi.target = pyipmi.Target(ipmb_address=0x20)
39
+
40
+ with ipmi:
41
+ print(ipmi.get_device_id())
42
+ """
43
+
44
+ from __future__ import annotations
19
45
 
20
46
  import time
21
47
  import ast
48
+ import warnings
49
+ from typing import Any, Literal
22
50
 
23
51
  from . import bmc
24
52
  from . import chassis
@@ -27,42 +55,58 @@ from . import event
27
55
  from . import fru
28
56
  from . import hpm
29
57
  from . import lan
58
+ from . import logger # noqa: F401 - installs the NullHandler
30
59
  from . import messaging
31
60
  from . import picmg
32
61
  from . import sdr
33
62
  from . import sel
34
63
  from . import sensor
64
+ from . import vita
35
65
  from . import msgs
36
66
 
37
67
  from .errors import IpmiTimeoutError, CompletionCodeError, RetryError
68
+ from .msgs import Message
38
69
  from .msgs.registry import create_request_by_name
39
70
  from .session import Session
40
71
  from .utils import check_rsp_completion_code, is_string
41
72
 
42
73
  try:
43
- from version import __version__
74
+ from .version import __version__
44
75
  except ImportError:
45
76
  __version__ = 'dev'
46
77
 
47
78
 
48
- def create_connection(interface):
79
+ def create_connection(interface: Any) -> Ipmi:
80
+ """Create a connection for an interface.
81
+
82
+ The connection gets a new :class:`pyipmi.session.Session` for the
83
+ interface. The target is not set, assign it to :attr:`Ipmi.target`
84
+ before sending requests.
85
+
86
+ Args:
87
+ interface: The interface, e.g. created by
88
+ :func:`pyipmi.interfaces.create_interface`.
89
+
90
+ Returns:
91
+ The connection.
92
+ """
49
93
  session = Session()
50
94
  session.interface = interface
51
95
  return Ipmi(interface=interface, session=session)
52
96
 
53
97
 
54
- class Requester(object):
98
+ class Requester:
55
99
  """The Requester class.
56
100
 
57
101
  This represents an IPMI device which initiates a request/response
58
102
  message exchange.
59
103
  """
60
104
 
61
- def __init__(self, ipmb_address):
105
+ def __init__(self, ipmb_address: int) -> None:
62
106
  self.ipmb_address = ipmb_address
63
107
 
64
108
 
65
- class NullRequester(object):
109
+ class NullRequester:
66
110
  """The NullRequester class.
67
111
 
68
112
  This requester is used for interfaces which doesn't require a valid
@@ -70,36 +114,48 @@ class NullRequester(object):
70
114
  """
71
115
 
72
116
  @property
73
- def ipmb_address(self):
117
+ def ipmb_address(self) -> int:
74
118
  raise AssertionError('NullRequester does not provide an IPMB address')
75
119
 
76
120
 
77
- class Routing(object):
78
- """The Target class represents an IPMI target."""
121
+ class Routing:
122
+ """One hop of the path to a target, see :meth:`Target.set_routing`."""
123
+
124
+ def __init__(self, rq_sa: int, rs_sa: int, channel: int | None) -> None:
125
+ """Initialize the hop.
79
126
 
80
- def __init__(self, rq_sa, rs_sa, channel):
127
+ Args:
128
+ rq_sa: The requester slave address.
129
+ rs_sa: The responder slave address.
130
+ channel: The channel of the bridge to the next hop, None for
131
+ the last hop.
132
+ """
81
133
  self.rq_sa = rq_sa
82
134
  self.rs_sa = rs_sa
83
135
  self.channel = channel
84
136
 
85
- def __str__(self):
137
+ def __str__(self) -> str:
86
138
  s = 'Routing: Rq: %s Rs: %s Ch: %s' \
87
139
  % (self.rq_sa, self.rs_sa, self.channel)
88
140
  return s
89
141
 
90
142
 
91
- class Target(object):
143
+ class Target:
92
144
  """The Target class represents an IPMI target."""
93
145
 
94
- routing = None
95
- ipmb_address = None
146
+ routing: list[Routing] | None = None
147
+ ipmb_address: int | None = None
96
148
 
97
- def __init__(self, ipmb_address=None, routing=None):
98
- """Initializer for the Target class.
149
+ def __init__(self, ipmb_address: int | None = None,
150
+ routing: str | list[tuple] | None = None) -> None:
151
+ """Initialize the target.
99
152
 
100
- `ipmb_address` is the IPMB target address
101
- `routing` is the bridging information used to build send message
102
- commands.
153
+ Args:
154
+ ipmb_address: The IPMB address of the target, e.g. 0x20 for
155
+ the BMC.
156
+ routing: The path over which the target is reachable, used to
157
+ build the bridged Send Message requests, see
158
+ :meth:`set_routing`.
103
159
  """
104
160
  if ipmb_address:
105
161
  self.ipmb_address = ipmb_address
@@ -107,49 +163,61 @@ class Target(object):
107
163
  if routing:
108
164
  self.set_routing(routing)
109
165
 
110
- def set_routing_information(self, routing):
166
+ def set_routing_information(self, routing: str | list[tuple]) -> None:
167
+ """Set the path over which a target is reachable.
168
+
169
+ An alias of :meth:`set_routing`.
170
+ """
111
171
  self.set_routing(routing)
112
172
 
113
- def set_routing(self, routing):
173
+ def set_routing(self, routing: str | list[tuple]) -> None:
114
174
  """Set the path over which a target is reachable.
115
175
 
116
- The path is given as a list of tuples in the form (address,
117
- bridge_channel).
176
+ Each hop of the path is a tuple ``(rq_sa, rs_sa, channel)``: the
177
+ requester address, the responder address and the channel of the
178
+ bridge to the next hop. The channel of the last hop is None.
179
+
180
+ Args:
181
+ routing: The list of hops, or its string representation as
182
+ given on the command line.
118
183
 
119
- Example #1: access to an ATCA blade in a chassis
120
- slave = 0x81, target = 0x82
121
- routing = [(0x81,0x20,0),(0x20,0x82,None)]
184
+ Example #1, access to an ATCA blade in a chassis (slave 0x81,
185
+ target 0x82)::
122
186
 
123
- Example #2: access to an AMC in a uTCA chassis
124
- slave = 0x81, target = 0x72
125
- routing = [(0x81,0x20,0),(0x20,0x82,7),(0x20,0x72,None)]
187
+ routing = [(0x81, 0x20, 0), (0x20, 0x82, None)]
126
188
 
189
+ Example #2, access to an AMC in a uTCA chassis (slave 0x81,
190
+ target 0x72)::
127
191
 
128
- uTCA - MCH AMC
129
- .-------------------. .--------.
130
- | .-----------| | |
131
- | ShMC | CM | | MMC |
132
- channel=0 | | | channel=7 | |
133
- 81 ------------| 0x20 |0x82 0x20 |-------------| 0x72 |
134
- | | | | |
135
- | | | | |
136
- | `-----------| | |
137
- `-------------------´ `--------´
138
- `------------´ `---´ `---------------´
192
+ routing = [(0x81, 0x20, 0), (0x20, 0x82, 7), (0x20, 0x72, None)]
139
193
 
140
- Example #3: access to an AMC in a ATCA AMC carrier
194
+ uTCA - MCH AMC
195
+ .-------------------. .--------.
196
+ | .-----------| | |
197
+ | ShMC | CM | | MMC |
198
+ channel=0 | | | channel=7 | |
199
+ 81 ------------| 0x20 |0x82 0x20 |-------------| 0x72 |
200
+ | | | | |
201
+ | | | | |
202
+ | `-----------| | |
203
+ `-------------------´ `--------´
204
+ `------------´ `---´ `---------------´
141
205
 
142
- slave = 0x81, target = 0x72
143
- routing = [(0x81,0x20,0),(0x20,0x8e,7),(0x20,0x80,None)]
206
+ Example #3, access to an AMC in an ATCA AMC carrier (slave 0x81,
207
+ target 0x72)::
144
208
 
209
+ routing = [(0x81, 0x20, 0), (0x20, 0x8e, 7), (0x20, 0x80, None)]
145
210
  """
146
211
  if is_string(routing):
147
212
  # if type(routing) in [unicode, str]:
148
213
  routing = ast.literal_eval(routing)
149
214
  self.routing = [Routing(*route) for route in routing]
150
215
 
151
- def __str__(self):
152
- string = 'Target: IPMB: 0x%02x\n' % self.ipmb_address
216
+ def __str__(self) -> str:
217
+ if self.ipmb_address is None:
218
+ string = 'Target: IPMB: none\n'
219
+ else:
220
+ string = 'Target: IPMB: 0x%02x\n' % self.ipmb_address
153
221
  if self.routing:
154
222
  for route in self.routing:
155
223
  string += ' %s\n' % route
@@ -158,10 +226,33 @@ class Target(object):
158
226
 
159
227
  class Ipmi(bmc.Bmc, chassis.Chassis, dcmi.Dcmi, fru.Fru, picmg.Picmg, hpm.Hpm,
160
228
  sdr.Sdr, sensor.Sensor, event.Event, sel.Sel, lan.Lan,
161
- messaging.Messaging):
229
+ messaging.Messaging, vita.Vita):
230
+ """A connection to an IPMI device.
162
231
 
163
- def __init__(self, interface=None, target=None, session=Session(),
164
- requester=NullRequester()):
232
+ The IPMI commands are the methods of this class, which it inherits
233
+ from the command groups, e.g. :meth:`get_device_id` from the one of
234
+ :mod:`pyipmi.bmc`. The requests are sent over the interface to
235
+ the target.
236
+
237
+ The connection is a context manager, which opens the interface and
238
+ establishes the session on entry and closes them on exit. Set up the
239
+ session before, see the example of :mod:`pyipmi`.
240
+ """
241
+
242
+ def __init__(self, interface: Any = None, target: Target | None = None,
243
+ session: Session | None = None,
244
+ requester: Any = None) -> None:
245
+ """Initialize the connection.
246
+
247
+ Args:
248
+ interface: The interface the requests are sent over.
249
+ target: The target of the requests.
250
+ session: The session, a new one if not given. The interface
251
+ of the session is set to ``interface``.
252
+ requester: The requester of the requests, needed by interfaces
253
+ that send the requests on the IPMB, a
254
+ :class:`NullRequester` if not given.
255
+ """
165
256
  self._interface = interface
166
257
 
167
258
  # we need a session, set if not passed
@@ -172,43 +263,98 @@ class Ipmi(bmc.Bmc, chassis.Chassis, dcmi.Dcmi, fru.Fru, picmg.Picmg, hpm.Hpm,
172
263
  self._session.interface = interface
173
264
 
174
265
  self._target = target
175
- self.requester = requester
266
+ self.requester = requester if requester is not None else NullRequester()
176
267
 
177
268
  for base in Ipmi.__bases__:
178
- base.__init__(self)
269
+ base.__init__(self) # type: ignore[misc]
179
270
 
180
- def __enter__(self):
271
+ def __enter__(self) -> Ipmi:
181
272
  self.open()
182
273
  return self
183
274
 
184
- def __exit__(self, exception_type, exception_value, traceback):
275
+ def __exit__(self, exception_type: Any, exception_value: Any,
276
+ traceback: Any) -> Literal[False]:
185
277
  self.close()
186
278
  return False
187
279
 
188
- def open(self):
280
+ def open(self) -> None:
281
+ """Open the interface and establish the session."""
189
282
  self.interface.open()
190
283
  if self.session is not None:
191
284
  self.session.establish()
192
285
 
193
- def close(self):
286
+ def close(self) -> None:
287
+ """Close the session and the interface."""
194
288
  if self.session is not None:
195
289
  self.session.close()
196
290
  self.interface.close()
197
291
 
198
- def is_ipmc_accessible(self):
199
- return self.interface.is_ipmc_accessible(self.target)
292
+ def is_target_accessible(self) -> bool:
293
+ """Check if the target answers.
200
294
 
201
- def wait_until_ipmb_is_accessible(self, timeout, interval=0.25):
295
+ Returns:
296
+ True if the target answers. Depending on the interface, False
297
+ is returned or an exception is raised if it does not.
298
+ """
299
+ return self.interface.is_target_accessible(self.target)
300
+
301
+ def is_ipmc_accessible(self) -> bool:
302
+ """Deprecated, the old name of :meth:`is_target_accessible`."""
303
+ warnings.warn('is_ipmc_accessible is deprecated, use '
304
+ 'is_target_accessible', DeprecationWarning,
305
+ stacklevel=2)
306
+ return self.is_target_accessible()
307
+
308
+ def wait_until_target_is_accessible(self, timeout: float,
309
+ interval: float = 0.25) -> None:
310
+ """Wait until the target is accessible.
311
+
312
+ Args:
313
+ timeout: The time to wait in seconds.
314
+ interval: The time between the checks in seconds.
315
+
316
+ Raises:
317
+ IpmiTimeoutError: The target is not accessible after the
318
+ timeout, if the interface raises it, see
319
+ :meth:`is_target_accessible`.
320
+ """
202
321
  start_time = time.time()
203
322
  while time.time() < start_time + (timeout):
204
323
  try:
205
- self.is_ipmc_accessible()
324
+ if self.is_target_accessible():
325
+ return
206
326
  except IpmiTimeoutError:
207
- time.sleep(interval)
327
+ pass
328
+ time.sleep(interval)
208
329
 
209
- self.is_ipmc_accessible()
330
+ self.is_target_accessible()
210
331
 
211
- def send_message(self, req, retry=3):
332
+ def wait_until_ipmb_is_accessible(self, timeout: float,
333
+ interval: float = 0.25) -> None:
334
+ """Deprecated, use :meth:`wait_until_target_is_accessible`."""
335
+ warnings.warn('wait_until_ipmb_is_accessible is deprecated, use '
336
+ 'wait_until_target_is_accessible', DeprecationWarning,
337
+ stacklevel=2)
338
+ self.wait_until_target_is_accessible(timeout, interval)
339
+
340
+ def send_message(self, req: Message, retry: int = 3) -> Message:
341
+ """Send a request to the target and return the response.
342
+
343
+ The request is sent again if the target is busy. The completion
344
+ code of the response is not checked.
345
+
346
+ Args:
347
+ req: The request message.
348
+ retry: The number of tries.
349
+
350
+ Returns:
351
+ The response message.
352
+
353
+ Raises:
354
+ RetryError: The target is still busy after ``retry`` tries.
355
+ CompletionCodeError: The interface failed with another
356
+ completion code, e.g. of a bridged Send Message request.
357
+ """
212
358
  req.target = self.target
213
359
  req.requester = self.requester
214
360
  rsp = None
@@ -221,12 +367,28 @@ class Ipmi(bmc.Bmc, chassis.Chassis, dcmi.Dcmi, fru.Fru, picmg.Picmg, hpm.Hpm,
221
367
  except CompletionCodeError as e:
222
368
  if e.cc == msgs.constants.CC_NODE_BUSY:
223
369
  continue
370
+ raise
224
371
  else:
225
372
  raise RetryError()
226
373
 
227
374
  return rsp
228
375
 
229
- def send_message_with_name(self, name, *args, **kwargs):
376
+ def send_message_by_name(self, name: str, *args: Any,
377
+ **kwargs: Any) -> Message:
378
+ """Send a request by its name and return the response.
379
+
380
+ Args:
381
+ name: The name of the request, e.g. ``'GetDeviceId'``.
382
+ *args: Not used.
383
+ **kwargs: The fields of the request, set as attributes.
384
+
385
+ Returns:
386
+ The response message.
387
+
388
+ Raises:
389
+ CompletionCodeError: The completion code of the response is
390
+ not successful.
391
+ """
230
392
  req = create_request_by_name(name)
231
393
 
232
394
  for key, value in kwargs.items():
@@ -236,45 +398,66 @@ class Ipmi(bmc.Bmc, chassis.Chassis, dcmi.Dcmi, fru.Fru, picmg.Picmg, hpm.Hpm,
236
398
  check_rsp_completion_code(rsp)
237
399
  return rsp
238
400
 
239
- def raw_command(self, lun, netfn, raw_bytes):
240
- """Send the raw command data and return the raw response.
401
+ def send_message_with_name(self, name: str, *args: Any,
402
+ **kwargs: Any) -> Message:
403
+ """Deprecated, the old name of :meth:`send_message_by_name`."""
404
+ warnings.warn('send_message_with_name is deprecated, use '
405
+ 'send_message_by_name', DeprecationWarning,
406
+ stacklevel=2)
407
+ return self.send_message_by_name(name, *args, **kwargs)
408
+
409
+ def send_raw(self, lun: int, netfn: int, raw_bytes: bytes) -> bytes:
410
+ """Send a raw request to the target and return the raw response.
241
411
 
242
- lun: the logical unit number
243
- netfn: the network function
244
- raw_bytes: the raw message as bytestring
412
+ Args:
413
+ lun: The logical unit number.
414
+ netfn: The network function.
415
+ raw_bytes: The request, starting with the command ID.
245
416
 
246
- Returns the response as bytestring.
417
+ Returns:
418
+ The response, starting with the completion code.
247
419
  """
248
420
  return self.interface.send_and_receive_raw(self.target, lun, netfn,
249
421
  raw_bytes)
250
422
 
251
- def _get_interface(self):
423
+ def raw_command(self, lun: int, netfn: int, raw_bytes: bytes) -> bytes:
424
+ """Deprecated, the old name of :meth:`send_raw`."""
425
+ warnings.warn('raw_command is deprecated, use send_raw',
426
+ DeprecationWarning, stacklevel=2)
427
+ return self.send_raw(lun, netfn, raw_bytes)
428
+
429
+ @property
430
+ def interface(self) -> Any:
431
+ """The interface the requests are sent over."""
252
432
  try:
253
433
  return self._interface
254
434
  except AttributeError:
255
- raise RuntimeError('No interface has been set')
435
+ raise RuntimeError('No interface has been set') from None
436
+
437
+ @interface.setter
438
+ def interface(self, interface: Any) -> None:
439
+ self._interface = interface
256
440
 
257
- def _get_session(self):
441
+ @property
442
+ def session(self) -> Session:
443
+ """The session of the connection."""
258
444
  try:
259
445
  return self._session
260
446
  except AttributeError:
261
- raise RuntimeError('No IPMI session has been set')
447
+ raise RuntimeError('No IPMI session has been set') from None
448
+
449
+ @session.setter
450
+ def session(self, session: Session) -> None:
451
+ self._session = session
262
452
 
263
- def _get_target(self):
453
+ @property
454
+ def target(self) -> Target | None:
455
+ """The target of the requests."""
264
456
  try:
265
457
  return self._target
266
458
  except AttributeError:
267
- raise RuntimeError('No IPMI target has been set')
268
-
269
- def _set_interface(self, interface):
270
- self._interface = interface
459
+ raise RuntimeError('No IPMI target has been set') from None
271
460
 
272
- def _set_session(self, session):
273
- self._session = session
274
-
275
- def _set_target(self, target):
461
+ @target.setter
462
+ def target(self, target: Target | None) -> None:
276
463
  self._target = target
277
-
278
- target = property(_get_target, _set_target)
279
- interface = property(_get_interface, _set_interface)
280
- session = property(_get_session, _set_session)