redfish-python-sdk 1.3.0__tar.gz → 1.3.1__tar.gz

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 (116) hide show
  1. {redfish_python_sdk-1.3.0/redfish_python_sdk.egg-info → redfish_python_sdk-1.3.1}/PKG-INFO +1 -1
  2. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/pyproject.toml +1 -1
  3. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1/redfish_python_sdk.egg-info}/PKG-INFO +1 -1
  4. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_python_sdk.egg-info/SOURCES.txt +2 -0
  5. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/__init__.py +1 -1
  6. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/client.py +2 -2
  7. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/http_client.py +11 -2
  8. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/chassis.py +14 -8
  9. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/event.py +5 -2
  10. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/log_collect_strategies/__init__.py +12 -3
  11. redfish_python_sdk-1.3.1/redfish_sdk/managers/log_collect_strategies/h3c.py +212 -0
  12. redfish_python_sdk-1.3.1/redfish_sdk/managers/log_collect_strategies/inspur.py +279 -0
  13. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/log_collect_strategies/lenovo.py +12 -7
  14. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/log_collect_strategies/zte.py +91 -51
  15. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/systems.py +105 -1
  16. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/update_strategies/vendor_detect.py +1 -0
  17. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/drive.py +11 -1
  18. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/fru.py +4 -1
  19. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/log_collect.py +25 -19
  20. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/oem.py +37 -4
  21. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/registry.py +6 -2
  22. redfish_python_sdk-1.3.1/tests/test_event_headers_mock.py +52 -0
  23. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_log_collect_mock.py +539 -33
  24. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_models.py +10 -0
  25. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_update_strategies.py +5 -0
  26. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_v1_1_apis_mock.py +2 -2
  27. redfish_python_sdk-1.3.0/redfish_sdk/managers/log_collect_strategies/inspur.py +0 -148
  28. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/LICENSE +0 -0
  29. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/README.md +0 -0
  30. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_python_sdk.egg-info/dependency_links.txt +0 -0
  31. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_python_sdk.egg-info/requires.txt +0 -0
  32. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_python_sdk.egg-info/top_level.txt +0 -0
  33. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/exceptions.py +0 -0
  34. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/__init__.py +0 -0
  35. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/_log_helpers.py +0 -0
  36. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/account.py +0 -0
  37. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/composition.py +0 -0
  38. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/log_collect_strategies/base.py +0 -0
  39. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/log_collect_strategies/enginetech.py +0 -0
  40. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/log_collect_strategies/registry.py +0 -0
  41. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/log_collect_strategies/smoothcompute.py +0 -0
  42. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/log_collect_strategies/xfusion.py +0 -0
  43. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/managers.py +0 -0
  44. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/oem_extractors/__init__.py +0 -0
  45. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/oem_extractors/base.py +0 -0
  46. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/oem_extractors/h3c.py +0 -0
  47. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/oem_extractors/inspur.py +0 -0
  48. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/oem_extractors/lenovo.py +0 -0
  49. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/oem_extractors/nettrix.py +0 -0
  50. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/oem_extractors/registry.py +0 -0
  51. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/oem_extractors/xfusion.py +0 -0
  52. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/oem_extractors/zte.py +0 -0
  53. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/registries.py +0 -0
  54. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/session.py +0 -0
  55. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/system_resources.py +0 -0
  56. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/task.py +0 -0
  57. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/telemetry.py +0 -0
  58. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/update.py +0 -0
  59. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/update_strategies/__init__.py +0 -0
  60. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/update_strategies/base.py +0 -0
  61. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/update_strategies/enginetech.py +0 -0
  62. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/update_strategies/h3c.py +0 -0
  63. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/update_strategies/inspur.py +0 -0
  64. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/update_strategies/lenovo.py +0 -0
  65. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/update_strategies/nettrix.py +0 -0
  66. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/update_strategies/registry.py +0 -0
  67. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/update_strategies/xfusion.py +0 -0
  68. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/managers/update_strategies/zte.py +0 -0
  69. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/__init__.py +0 -0
  70. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/account.py +0 -0
  71. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/actions.py +0 -0
  72. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/chassis.py +0 -0
  73. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/check.py +0 -0
  74. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/common.py +0 -0
  75. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/composition.py +0 -0
  76. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/event.py +0 -0
  77. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/gpu.py +0 -0
  78. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/logs.py +0 -0
  79. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/managers.py +0 -0
  80. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/memory.py +0 -0
  81. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/network_adapter.py +0 -0
  82. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/pcie_device.py +0 -0
  83. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/power.py +0 -0
  84. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/processor.py +0 -0
  85. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/resource_key.py +0 -0
  86. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/root.py +0 -0
  87. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/session.py +0 -0
  88. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/storage.py +0 -0
  89. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/system_resources.py +0 -0
  90. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/systems.py +0 -0
  91. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/task.py +0 -0
  92. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/telemetry.py +0 -0
  93. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/thermal.py +0 -0
  94. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/redfish_sdk/models/update.py +0 -0
  95. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/setup.cfg +0 -0
  96. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_action_models.py +0 -0
  97. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_chassis_discovery.py +0 -0
  98. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_client_mock.py +0 -0
  99. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_composition_mock.py +0 -0
  100. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_exception_hints_mock.py +0 -0
  101. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_firmware_upload_mock.py +0 -0
  102. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_http_retry_mock.py +0 -0
  103. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_log_entries_mock.py +0 -0
  104. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_manager_actions_mock.py +0 -0
  105. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_manager_cache_lifecycle.py +0 -0
  106. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_manager_discovery.py +0 -0
  107. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_manager_network_protocol.py +0 -0
  108. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_models_mock.py +0 -0
  109. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_nested_discovery.py +0 -0
  110. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_oem_extractors.py +0 -0
  111. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_offline_json.py +0 -0
  112. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_real_bmc.py +0 -0
  113. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_sdk_mock.py +0 -0
  114. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_serial_interfaces_mock.py +0 -0
  115. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_system_resources_mock.py +0 -0
  116. {redfish_python_sdk-1.3.0 → redfish_python_sdk-1.3.1}/tests/test_telemetry_mock.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redfish-python-sdk
3
- Version: 1.3.0
3
+ Version: 1.3.1
4
4
  Summary: A Python SDK for interacting with Redfish-compliant BMC endpoints
5
5
  Author: RedNote Infrastructure
6
6
  License: BSD 3-Clause License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "redfish-python-sdk"
7
- version = "1.3.0"
7
+ version = "1.3.1"
8
8
  description = "A Python SDK for interacting with Redfish-compliant BMC endpoints"
9
9
  requires-python = ">=3.9"
10
10
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redfish-python-sdk
3
- Version: 1.3.0
3
+ Version: 1.3.1
4
4
  Summary: A Python SDK for interacting with Redfish-compliant BMC endpoints
5
5
  Author: RedNote Infrastructure
6
6
  License: BSD 3-Clause License
@@ -27,6 +27,7 @@ redfish_sdk/managers/update.py
27
27
  redfish_sdk/managers/log_collect_strategies/__init__.py
28
28
  redfish_sdk/managers/log_collect_strategies/base.py
29
29
  redfish_sdk/managers/log_collect_strategies/enginetech.py
30
+ redfish_sdk/managers/log_collect_strategies/h3c.py
30
31
  redfish_sdk/managers/log_collect_strategies/inspur.py
31
32
  redfish_sdk/managers/log_collect_strategies/lenovo.py
32
33
  redfish_sdk/managers/log_collect_strategies/registry.py
@@ -88,6 +89,7 @@ tests/test_action_models.py
88
89
  tests/test_chassis_discovery.py
89
90
  tests/test_client_mock.py
90
91
  tests/test_composition_mock.py
92
+ tests/test_event_headers_mock.py
91
93
  tests/test_exception_hints_mock.py
92
94
  tests/test_firmware_upload_mock.py
93
95
  tests/test_http_retry_mock.py
@@ -68,7 +68,7 @@ from .models.systems import BootOption
68
68
  from .models.telemetry import MetricDefinition, MetricReport, MetricReportDefinition, TelemetryService, Triggers
69
69
  from .models.update import FirmwareUpdateResponse
70
70
 
71
- __version__ = "1.3.0"
71
+ __version__ = "1.3.1"
72
72
  __author__ = "RedNote Infrastructure"
73
73
 
74
74
  __all__ = [
@@ -1653,8 +1653,8 @@ class RedfishClient:
1653
1653
  context: Optional context string identifying the subscription.
1654
1654
  protocol: Wire protocol; defaults to ``"Redfish"``.
1655
1655
  http_headers: Optional headers to send on the callback POST.
1656
- Pass either a ``dict`` or a ``list[dict]`` — both forms are
1657
- commonly seen across BMC vendors.
1656
+ Pass a ``dict`` or a ``list[dict]``. Dictionaries are converted
1657
+ to the standard array form; ``raw_body`` preserves vendor forms.
1658
1658
  origin_resources: Optional list of ``{"@odata.id": "..."}``.
1659
1659
  subscription_type: Optional ``SubscriptionType`` value.
1660
1660
  registry_prefixes: Optional message-registry filter list.
@@ -307,8 +307,17 @@ class RedfishHttpClient:
307
307
  except Exception:
308
308
  pass
309
309
 
310
- logger.error("Request failed: %s %s -> HTTP %d, body: %s",
311
- response.request.method, path, code, body[:500])
310
+ # 404 ("not found") is routinely an expected outcome handled by callers
311
+ # via fallback (e.g. Chassis/1 -> Chassis/Self) or treated as "absent"
312
+ # (e.g. optional sub-resources). Logging it at ERROR produces alarming
313
+ # noise for what is normal control flow, so it is recorded at DEBUG only.
314
+ # All other non-2xx responses remain at ERROR.
315
+ if code == 404:
316
+ logger.debug("Request returned 404 (not found): %s %s",
317
+ response.request.method, path)
318
+ else:
319
+ logger.error("Request failed: %s %s -> HTTP %d, body: %s",
320
+ response.request.method, path, code, body[:500])
312
321
 
313
322
  if code in (401, 403):
314
323
  raise RedfishAuthError(code)
@@ -197,7 +197,20 @@ class ChassisManager:
197
197
  """
198
198
  chassis = self.get(chassis_id)
199
199
 
200
- # Strategy 1: Use Chassis.Links.Drives
200
+ # Strategy 1: Chassis.Drives (canonical chassis-owned drive collection).
201
+ # Preferred because some BMCs expose the authoritative Drive resources
202
+ # (with fields such as IndicatorLED) only here, while Chassis.Links.Drives
203
+ # may resolve to Storage-side Drive references that lack those fields.
204
+ if chassis.drives:
205
+ try:
206
+ drives = self._client._get_collection(chassis.drives.odata_id, Drive)
207
+ if drives:
208
+ return drives
209
+ except RedfishException as exc:
210
+ logger.warning("Chassis.Drives collection failed: %s", exc)
211
+
212
+ # Strategy 2: Chassis.Links.Drives (links may resolve to Storage-side
213
+ # Drive resources). Kept as fallback for BMCs that only populate links.
201
214
  if chassis.links and chassis.links.drives:
202
215
  drive_links = chassis.links.drives
203
216
  if drive_links:
@@ -223,13 +236,6 @@ class ChassisManager:
223
236
  if result:
224
237
  return result
225
238
 
226
- # Strategy 2: Use Chassis.Drives collection endpoint
227
- if chassis.drives:
228
- try:
229
- return self._client._get_collection(chassis.drives.odata_id, Drive)
230
- except RedfishException as exc:
231
- logger.warning("Chassis.Drives collection failed: %s", exc)
232
-
233
239
  return []
234
240
 
235
241
  def network_adapters(self, chassis_id: Optional[str] = None) -> List[NetworkAdapter]:
@@ -135,7 +135,8 @@ class EventServiceManager:
135
135
  protocol: Wire protocol; defaults to ``"Redfish"``.
136
136
  http_headers: Optional headers to be sent on the callback POST.
137
137
  Pass a ``dict`` (``{"X-Auth-Token": "..."}``) or a
138
- ``list[dict]`` — both forms are common across BMC vendors.
138
+ ``list[dict]``. Dictionaries are serialized as the standard
139
+ array of header objects. Use ``raw_body`` for vendor shapes.
139
140
  origin_resources: Optional list of ``{"@odata.id": "..."}``
140
141
  filtering the resources of interest.
141
142
  subscription_type: Optional ``SubscriptionType`` value
@@ -167,7 +168,9 @@ class EventServiceManager:
167
168
  if context is not None:
168
169
  body["Context"] = context
169
170
  if http_headers is not None:
170
- body["HttpHeaders"] = http_headers
171
+ body["HttpHeaders"] = (
172
+ [http_headers] if isinstance(http_headers, dict) else http_headers
173
+ )
171
174
  if origin_resources is not None:
172
175
  body["OriginResources"] = origin_resources
173
176
  if subscription_type is not None:
@@ -10,13 +10,17 @@ Architecture:
10
10
  BaseLogCollectStrategy (ABC)
11
11
  ├── GenericLogCollectStrategy — standard Redfish (DMTF) default
12
12
  ├── XFusionLogCollectStrategy — xFusion (超聚变), OEM diagnostic body
13
+ ├── H3cLogCollectStrategy — H3C (新华三), Manager.Dump +
14
+ Manager.GeneralDownload SDS flow
13
15
  ├── InspurLogCollectStrategy — Inspur (浪潮), collection-level
14
16
  CollectAllLog / DownloadAllLog pair
17
+ │ └── IeitLogCollectStrategy — IEIT SYSTEMS, member actions +
18
+ in-flight Task reuse
15
19
  ├── LenovoLogCollectStrategy — Lenovo (联想, AMI-based),
16
20
  CollectAllLog + private
17
21
  CollectProgress + DownloadAllLog
18
22
  ├── ZteLogCollectStrategy — ZTE (中兴), Dump + Dump/Progress +
19
- GeneralDownload
23
+ ExportLogs
20
24
  └── SmoothcomputeLogCollectStrategy — smoothcompute (顺算), OEM
21
25
  DiagnosticService.CollectBlackBox /
22
26
  ExportBlackBox (trigger returns a
@@ -25,7 +29,7 @@ Architecture:
25
29
 
26
30
  Only vendors that genuinely differ from the standard DMTF behaviour get a
27
31
  dedicated strategy. Vendors whose ``CollectDiagnosticData`` matches the
28
- standard body (e.g. Lenovo, Nettrix) intentionally have no file and fall back
32
+ standard body (e.g. Nettrix) intentionally have no file and fall back
29
33
  to :class:`GenericLogCollectStrategy` via the registry.
30
34
 
31
35
  Vendor detection is shared with the firmware ``update_strategies`` package
@@ -37,7 +41,8 @@ All strategies are auto-registered when this package is imported.
37
41
  from ..update_strategies.vendor_detect import VendorDetector
38
42
  from .base import BaseLogCollectStrategy, GenericLogCollectStrategy
39
43
  from .enginetech import EnginetechLogCollectStrategy
40
- from .inspur import InspurLogCollectStrategy
44
+ from .h3c import H3cLogCollectStrategy
45
+ from .inspur import IeitLogCollectStrategy, InspurLogCollectStrategy
41
46
  from .lenovo import LenovoLogCollectStrategy
42
47
  from .registry import LogCollectStrategyRegistry
43
48
  from .smoothcompute import SmoothcomputeLogCollectStrategy
@@ -48,7 +53,9 @@ from .zte import ZteLogCollectStrategy
48
53
  LogCollectStrategyRegistry.register("generic", GenericLogCollectStrategy())
49
54
  LogCollectStrategyRegistry.register("enginetech", EnginetechLogCollectStrategy())
50
55
  LogCollectStrategyRegistry.register("xfusion", XFusionLogCollectStrategy())
56
+ LogCollectStrategyRegistry.register("h3c", H3cLogCollectStrategy())
51
57
  LogCollectStrategyRegistry.register("inspur", InspurLogCollectStrategy())
58
+ LogCollectStrategyRegistry.register("ieit", IeitLogCollectStrategy())
52
59
  LogCollectStrategyRegistry.register("lenovo", LenovoLogCollectStrategy())
53
60
  LogCollectStrategyRegistry.register("zte", ZteLogCollectStrategy())
54
61
  LogCollectStrategyRegistry.register(
@@ -60,6 +67,8 @@ __all__ = [
60
67
  "GenericLogCollectStrategy",
61
68
  "EnginetechLogCollectStrategy",
62
69
  "XFusionLogCollectStrategy",
70
+ "H3cLogCollectStrategy",
71
+ "IeitLogCollectStrategy",
63
72
  "InspurLogCollectStrategy",
64
73
  "LenovoLogCollectStrategy",
65
74
  "ZteLogCollectStrategy",
@@ -0,0 +1,212 @@
1
+ """H3C (新华三) SDS diagnostic-log collection strategy.
2
+
3
+ H3C HDM does not expose the standard per-LogService
4
+ ``#LogService.CollectDiagnosticData`` action. The Manager instead publishes
5
+ OEM ``Manager.Dump`` and ``Manager.GeneralDownload`` actions. Collection
6
+ creates an asynchronous Redfish Task; after it completes, the SDS archive is
7
+ downloaded with a second POST.
8
+
9
+ Observed on H3C HDM firmware 2.09, ``#Manager.GeneralDownload`` is advertised
10
+ as an empty object without a ``target``. For that specific malformed action
11
+ descriptor, this strategy derives the conventional action URI from the
12
+ advertised Manager resource rather than treating an unadvertised action as
13
+ available.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ from typing import TYPE_CHECKING, Any, Dict, Optional
19
+
20
+ from ...models.common import Collection, Link
21
+ from ...models.managers import Manager
22
+ from ...models.task import Task
23
+ from .base import BaseLogCollectStrategy
24
+
25
+ if TYPE_CHECKING:
26
+ from ...client import RedfishClient
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ _DUMP_ACTION = "#Manager.Dump"
31
+ _DOWNLOAD_ACTION = "#Manager.GeneralDownload"
32
+ _SDS_PATH = "/mnt/sdmmc0p1/sds/bmc/dump.tar.gz"
33
+
34
+
35
+ class H3cLogCollectStrategy(BaseLogCollectStrategy):
36
+ """Collect and download H3C SDS bundles through Manager OEM actions."""
37
+
38
+ def find_existing_task(
39
+ self,
40
+ client: "RedfishClient",
41
+ log_services_odata_id: str,
42
+ manager_id: Optional[str] = None,
43
+ ) -> Optional[Task]:
44
+ """Always create a fresh SDS bundle at H3C's fixed artifact path."""
45
+ return None
46
+
47
+ def discover_collect_target(
48
+ self,
49
+ client: "RedfishClient",
50
+ log_services_odata_id: str,
51
+ log_id: Optional[str],
52
+ ) -> Optional[str]:
53
+ """Return the Manager-level ``Manager.Dump`` action target."""
54
+ manager = self._manager_for_log_services(client, log_services_odata_id)
55
+ advertised, target = self._manager_oem_action(manager, _DUMP_ACTION)
56
+ return target if advertised else None
57
+
58
+ def build_collect_body(
59
+ self,
60
+ diagnostic_data_type: Optional[str] = None,
61
+ oem_params: Optional[Dict[str, Any]] = None,
62
+ ) -> Dict[str, Any]:
63
+ """Build H3C's request for generating an SDS archive on the BMC."""
64
+ body: Dict[str, Any] = {
65
+ "Type": "URI",
66
+ "Content": _SDS_PATH,
67
+ }
68
+ if diagnostic_data_type:
69
+ body["LogType"] = diagnostic_data_type
70
+ if oem_params:
71
+ body.update(oem_params)
72
+ return body
73
+
74
+ def trigger(
75
+ self,
76
+ client: "RedfishClient",
77
+ log_services_odata_id: str,
78
+ *,
79
+ log_id: Optional[str] = None,
80
+ diagnostic_data_type: Optional[str] = None,
81
+ oem_params: Optional[Dict[str, Any]] = None,
82
+ manager_id: Optional[str] = None,
83
+ ) -> Task:
84
+ """POST ``Manager.Dump`` and convert its task collection response."""
85
+ from ...exceptions import RedfishValidationError
86
+
87
+ manager = self._manager_for_log_services(client, log_services_odata_id)
88
+ advertised, target = self._manager_oem_action(manager, _DUMP_ACTION)
89
+ if not advertised or not target:
90
+ raise RedfishValidationError(
91
+ f"H3C Manager {manager.odata_id!r} does not expose "
92
+ f"{_DUMP_ACTION} with an action target"
93
+ )
94
+
95
+ body = self.build_collect_body(diagnostic_data_type, oem_params)
96
+ logger.info("H3C Manager.Dump POST %s body=%s", target, body)
97
+ response = client._http_client.post(
98
+ target, Collection[Link], raw_body=body
99
+ )
100
+
101
+ task_url = next(
102
+ (
103
+ member.odata_id
104
+ for member in response.members or []
105
+ if member.odata_id
106
+ ),
107
+ None,
108
+ )
109
+ if not task_url and response.odata_id and "/Tasks/" in response.odata_id:
110
+ task_url = response.odata_id
111
+ if not task_url:
112
+ raise RedfishValidationError(
113
+ "H3C Manager.Dump did not return a Redfish Task reference"
114
+ )
115
+
116
+ return Task.model_construct(
117
+ id=task_url.rstrip("/").rsplit("/", 1)[-1],
118
+ odata_id=task_url,
119
+ _h3c_manager_id=self._manager_id(manager),
120
+ )
121
+
122
+ def wait_until_ready(
123
+ self,
124
+ client: "RedfishClient",
125
+ task: Task,
126
+ poll_interval: int = 5,
127
+ timeout: int = 1800,
128
+ ) -> Task:
129
+ """Wait through TaskService while retaining the owning Manager id."""
130
+ finished = super().wait_until_ready(
131
+ client, task, poll_interval=poll_interval, timeout=timeout
132
+ )
133
+ manager_id = getattr(task, "_h3c_manager_id", None)
134
+ if manager_id:
135
+ finished = finished.model_copy(
136
+ update={"_h3c_manager_id": manager_id}
137
+ )
138
+ return finished
139
+
140
+ def download_artifact(
141
+ self,
142
+ client: "RedfishClient",
143
+ task: Task,
144
+ output_path: Optional[str] = None,
145
+ ) -> "bytes | str":
146
+ """POST ``Manager.GeneralDownload`` and save the returned archive."""
147
+ from ...exceptions import RedfishValidationError
148
+
149
+ manager_id = getattr(task, "_h3c_manager_id", None)
150
+ manager = client.get_manager(manager_id)
151
+ advertised, target = self._manager_oem_action(
152
+ manager, _DOWNLOAD_ACTION
153
+ )
154
+ if not advertised:
155
+ raise RedfishValidationError(
156
+ f"H3C Manager {manager.odata_id!r} does not advertise "
157
+ f"{_DOWNLOAD_ACTION}"
158
+ )
159
+ if not target and manager.odata_id:
160
+ target = (
161
+ f"{manager.odata_id.rstrip('/')}"
162
+ "/Actions/Oem/Public/Manager.GeneralDownload"
163
+ )
164
+ if not target:
165
+ raise RedfishValidationError(
166
+ f"Could not resolve {_DOWNLOAD_ACTION} for H3C Manager"
167
+ )
168
+
169
+ body = {
170
+ "TransferProtocol": "HTTPS",
171
+ "Content": _SDS_PATH,
172
+ }
173
+ logger.info("H3C Manager.GeneralDownload POST %s", target)
174
+ return client._http_client.download_via_post(
175
+ target, output_path, raw_body=body
176
+ )
177
+
178
+ @staticmethod
179
+ def _manager_for_log_services(
180
+ client: "RedfishClient", log_services_odata_id: str
181
+ ) -> Manager:
182
+ """Resolve the Manager owning an advertised LogServices collection."""
183
+ suffix = "/LogServices"
184
+ manager_url = log_services_odata_id.rstrip("/")
185
+ if manager_url.endswith(suffix):
186
+ manager_url = manager_url[: -len(suffix)]
187
+ manager_id = manager_url.rstrip("/").rsplit("/", 1)[-1]
188
+ return client.get_manager(manager_id)
189
+
190
+ @staticmethod
191
+ def _manager_id(manager: Manager) -> Optional[str]:
192
+ if manager.odata_id:
193
+ return manager.odata_id.rstrip("/").rsplit("/", 1)[-1]
194
+ return str(manager.id) if manager.id is not None else None
195
+
196
+ @staticmethod
197
+ def _manager_oem_action(
198
+ manager: Manager, action_name: str
199
+ ) -> tuple[bool, Optional[str]]:
200
+ """Return whether an H3C OEM action is advertised and its target."""
201
+ if manager.actions is None:
202
+ return False, None
203
+ actions = manager.actions.model_dump(by_alias=True, exclude_none=True)
204
+ oem = actions.get("Oem")
205
+ public = oem.get("Public") if isinstance(oem, dict) else None
206
+ if not isinstance(public, dict) or action_name not in public:
207
+ return False, None
208
+ descriptor = public[action_name]
209
+ if not isinstance(descriptor, dict):
210
+ return True, None
211
+ target = descriptor.get("target")
212
+ return True, target if isinstance(target, str) and target else None