redfish-python-sdk 1.0.0__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.
- redfish_python_sdk-1.0.0.dist-info/METADATA +164 -0
- redfish_python_sdk-1.0.0.dist-info/RECORD +56 -0
- redfish_python_sdk-1.0.0.dist-info/WHEEL +5 -0
- redfish_python_sdk-1.0.0.dist-info/licenses/LICENSE +29 -0
- redfish_python_sdk-1.0.0.dist-info/top_level.txt +1 -0
- redfish_sdk/__init__.py +63 -0
- redfish_sdk/client.py +1894 -0
- redfish_sdk/exceptions.py +56 -0
- redfish_sdk/http_client.py +452 -0
- redfish_sdk/managers/__init__.py +21 -0
- redfish_sdk/managers/_log_helpers.py +144 -0
- redfish_sdk/managers/account.py +96 -0
- redfish_sdk/managers/chassis.py +327 -0
- redfish_sdk/managers/event.py +264 -0
- redfish_sdk/managers/managers.py +120 -0
- redfish_sdk/managers/registries.py +48 -0
- redfish_sdk/managers/session.py +130 -0
- redfish_sdk/managers/systems.py +630 -0
- redfish_sdk/managers/task.py +89 -0
- redfish_sdk/managers/update.py +99 -0
- redfish_sdk/managers/update_strategies/__init__.py +51 -0
- redfish_sdk/managers/update_strategies/base.py +101 -0
- redfish_sdk/managers/update_strategies/h3c.py +140 -0
- redfish_sdk/managers/update_strategies/inspur.py +89 -0
- redfish_sdk/managers/update_strategies/lenovo.py +59 -0
- redfish_sdk/managers/update_strategies/nettrix.py +56 -0
- redfish_sdk/managers/update_strategies/registry.py +72 -0
- redfish_sdk/managers/update_strategies/vendor_detect.py +111 -0
- redfish_sdk/managers/update_strategies/xfusion.py +60 -0
- redfish_sdk/managers/update_strategies/zte.py +68 -0
- redfish_sdk/models/__init__.py +55 -0
- redfish_sdk/models/account.py +60 -0
- redfish_sdk/models/chassis.py +86 -0
- redfish_sdk/models/check.py +231 -0
- redfish_sdk/models/common.py +102 -0
- redfish_sdk/models/drive.py +53 -0
- redfish_sdk/models/event.py +64 -0
- redfish_sdk/models/fru.py +59 -0
- redfish_sdk/models/gpu.py +33 -0
- redfish_sdk/models/logs.py +56 -0
- redfish_sdk/models/managers.py +153 -0
- redfish_sdk/models/memory.py +50 -0
- redfish_sdk/models/network_adapter.py +76 -0
- redfish_sdk/models/oem.py +173 -0
- redfish_sdk/models/pcie_device.py +89 -0
- redfish_sdk/models/power.py +92 -0
- redfish_sdk/models/processor.py +50 -0
- redfish_sdk/models/registry.py +34 -0
- redfish_sdk/models/resource_key.py +70 -0
- redfish_sdk/models/root.py +50 -0
- redfish_sdk/models/session.py +42 -0
- redfish_sdk/models/storage.py +77 -0
- redfish_sdk/models/systems.py +194 -0
- redfish_sdk/models/task.py +54 -0
- redfish_sdk/models/thermal.py +111 -0
- redfish_sdk/models/update.py +55 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Task service manager — manages asynchronous tasks.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import time
|
|
9
|
+
from typing import TYPE_CHECKING, List
|
|
10
|
+
|
|
11
|
+
from ..models.task import Task
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ..client import RedfishClient
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TaskServiceManager:
|
|
20
|
+
"""
|
|
21
|
+
Manages Redfish Task resources.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, client: RedfishClient):
|
|
27
|
+
self._client = client
|
|
28
|
+
self._http = client._http_client
|
|
29
|
+
|
|
30
|
+
def tasks(self) -> List[Task]:
|
|
31
|
+
"""
|
|
32
|
+
Get the list of all tasks.
|
|
33
|
+
"""
|
|
34
|
+
task_service = self._client._get_task_service()
|
|
35
|
+
return self._client._get_collection(task_service.tasks.odata_id, Task)
|
|
36
|
+
|
|
37
|
+
def get(self, task_id: str) -> Task:
|
|
38
|
+
"""
|
|
39
|
+
Get a specific task by ID.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
task_id: Task ID
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Task resource
|
|
46
|
+
"""
|
|
47
|
+
task_service = self._client._get_task_service()
|
|
48
|
+
return self._http.get(f"{task_service.tasks.odata_id}/{task_id}", Task)
|
|
49
|
+
|
|
50
|
+
def wait_for_task(
|
|
51
|
+
self,
|
|
52
|
+
task_id: str,
|
|
53
|
+
poll_interval: int = 5,
|
|
54
|
+
timeout: int = 600,
|
|
55
|
+
) -> Task:
|
|
56
|
+
"""
|
|
57
|
+
Poll a task until it completes or times out.
|
|
58
|
+
|
|
59
|
+
Useful for monitoring long-running firmware update tasks.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
task_id: Task ID to monitor
|
|
63
|
+
poll_interval: Seconds between polls (default 5)
|
|
64
|
+
timeout: Maximum wait time in seconds (default 600)
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Completed Task resource
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
TimeoutError: If task does not complete within timeout
|
|
71
|
+
"""
|
|
72
|
+
elapsed = 0
|
|
73
|
+
while elapsed < timeout:
|
|
74
|
+
task = self.get(task_id)
|
|
75
|
+
state = task.task_state or ""
|
|
76
|
+
logger.info(
|
|
77
|
+
"Task %s: state=%s, percent=%s%%",
|
|
78
|
+
task_id, state, task.percent_complete
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if state in ("Completed", "Exception", "Killed", "Cancelled"):
|
|
82
|
+
return task
|
|
83
|
+
|
|
84
|
+
time.sleep(poll_interval)
|
|
85
|
+
elapsed += poll_interval
|
|
86
|
+
|
|
87
|
+
raise TimeoutError(
|
|
88
|
+
f"Task {task_id} did not complete within {timeout} seconds"
|
|
89
|
+
)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Update service manager — manages firmware updates.
|
|
3
|
+
|
|
4
|
+
Provides:
|
|
5
|
+
- Firmware inventory listing
|
|
6
|
+
- Client certificate listing
|
|
7
|
+
- Firmware update (SimpleUpdate) with multi-vendor strategy support
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from typing import TYPE_CHECKING, Any, List, Optional
|
|
14
|
+
|
|
15
|
+
from ..models.common import RedfishResponse
|
|
16
|
+
from ..models.update import ClientCertificate, FirmwareInventory
|
|
17
|
+
from .update_strategies import UpdateStrategyRegistry, VendorDetector
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from ..client import RedfishClient
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class UpdateServiceManager:
|
|
26
|
+
"""
|
|
27
|
+
Manages Redfish Update service resources.
|
|
28
|
+
|
|
29
|
+
SimpleUpdate automatically detects the server vendor and applies
|
|
30
|
+
the appropriate request body format. Users can override the vendor
|
|
31
|
+
detection by passing the ``vendor`` parameter.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, client: RedfishClient):
|
|
35
|
+
self._client = client
|
|
36
|
+
self._http = client._http_client
|
|
37
|
+
|
|
38
|
+
def firmware_inventory(self) -> List[FirmwareInventory]:
|
|
39
|
+
"""
|
|
40
|
+
Get the list of firmware inventory entries.
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
update_service = self._client._get_update_service()
|
|
44
|
+
return self._client._get_collection(
|
|
45
|
+
update_service.firmware_inventory.odata_id, FirmwareInventory
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def client_certificates(self) -> List[ClientCertificate]:
|
|
49
|
+
"""
|
|
50
|
+
Get the list of client certificates for firmware update authentication.
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
update_service = self._client._get_update_service()
|
|
54
|
+
return self._client._get_collection(
|
|
55
|
+
update_service.client_certificates.odata_id, ClientCertificate
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def simple_update(
|
|
59
|
+
self,
|
|
60
|
+
image_uri: str,
|
|
61
|
+
transfer_protocol: str = "HTTP",
|
|
62
|
+
targets: Optional[list] = None,
|
|
63
|
+
vendor: Optional[str] = None,
|
|
64
|
+
**kwargs: Any,
|
|
65
|
+
) -> RedfishResponse:
|
|
66
|
+
"""
|
|
67
|
+
Trigger a firmware update via a remote image URI (e.g., NFS, HTTP).
|
|
68
|
+
|
|
69
|
+
Automatically detects the server vendor and uses the appropriate
|
|
70
|
+
request body format. The vendor can be manually overridden.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
image_uri: URI of the firmware image (e.g., "http://nas/fw/bmc.bin")
|
|
74
|
+
transfer_protocol: Transfer protocol (e.g., "HTTP", "NFS", "TFTP")
|
|
75
|
+
targets: Optional list of firmware target paths
|
|
76
|
+
(e.g., ["/redfish/v1/Managers/1"] or ["ActiveBMC"])
|
|
77
|
+
vendor: Optional vendor override (e.g., "inspur", "lenovo").
|
|
78
|
+
If not set, the vendor is auto-detected.
|
|
79
|
+
**kwargs: Vendor-specific parameters. Common ones include:
|
|
80
|
+
- username (str): File server username (Inspur, Lenovo, H3C)
|
|
81
|
+
- password (str): File server password (Inspur, Lenovo, H3C)
|
|
82
|
+
- preserve_config (bool): Preserve configuration during update
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
RedfishResponse (may contain a task reference for async update)
|
|
86
|
+
"""
|
|
87
|
+
detected_vendor = vendor or VendorDetector.detect(self._client)
|
|
88
|
+
strategy = UpdateStrategyRegistry.get(detected_vendor)
|
|
89
|
+
|
|
90
|
+
logger.info(
|
|
91
|
+
"SimpleUpdate: vendor=%s, strategy=%s, ImageURI=%s",
|
|
92
|
+
detected_vendor,
|
|
93
|
+
type(strategy).__name__,
|
|
94
|
+
image_uri,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return strategy.execute(
|
|
98
|
+
self._client, image_uri, transfer_protocol, targets, **kwargs
|
|
99
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Update strategies for multi-vendor firmware update support.
|
|
3
|
+
|
|
4
|
+
This package implements the Strategy Pattern for SimpleUpdate, allowing
|
|
5
|
+
each server vendor to have its own body-construction logic while keeping
|
|
6
|
+
a single entry point in UpdateServiceManager.simple_update().
|
|
7
|
+
|
|
8
|
+
Architecture:
|
|
9
|
+
BaseUpdateStrategy (ABC)
|
|
10
|
+
├── GenericUpdateStrategy — standard Redfish fallback
|
|
11
|
+
├── InspurUpdateStrategy — Inspur (浪潮)
|
|
12
|
+
├── ZteUpdateStrategy — ZTE (中兴)
|
|
13
|
+
├── H3cUpdateStrategy — H3C (新华三)
|
|
14
|
+
├── NettrixUpdateStrategy — Nettrix (宁畅)
|
|
15
|
+
├── XFusionUpdateStrategy — xFusion (超聚变)
|
|
16
|
+
└── LenovoUpdateStrategy — Lenovo (联想)
|
|
17
|
+
|
|
18
|
+
All strategies are auto-registered when this package is imported.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from .base import BaseUpdateStrategy, GenericUpdateStrategy
|
|
22
|
+
from .h3c import H3cUpdateStrategy
|
|
23
|
+
from .inspur import InspurUpdateStrategy
|
|
24
|
+
from .lenovo import LenovoUpdateStrategy
|
|
25
|
+
from .nettrix import NettrixUpdateStrategy
|
|
26
|
+
from .registry import UpdateStrategyRegistry
|
|
27
|
+
from .vendor_detect import VendorDetector
|
|
28
|
+
from .xfusion import XFusionUpdateStrategy
|
|
29
|
+
from .zte import ZteUpdateStrategy
|
|
30
|
+
|
|
31
|
+
# --- Auto-register all vendor strategies ---
|
|
32
|
+
UpdateStrategyRegistry.register("generic", GenericUpdateStrategy())
|
|
33
|
+
UpdateStrategyRegistry.register("inspur", InspurUpdateStrategy())
|
|
34
|
+
UpdateStrategyRegistry.register("zte", ZteUpdateStrategy())
|
|
35
|
+
UpdateStrategyRegistry.register("h3c", H3cUpdateStrategy())
|
|
36
|
+
UpdateStrategyRegistry.register("nettrix", NettrixUpdateStrategy())
|
|
37
|
+
UpdateStrategyRegistry.register("xfusion", XFusionUpdateStrategy())
|
|
38
|
+
UpdateStrategyRegistry.register("lenovo", LenovoUpdateStrategy())
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"BaseUpdateStrategy",
|
|
42
|
+
"GenericUpdateStrategy",
|
|
43
|
+
"InspurUpdateStrategy",
|
|
44
|
+
"ZteUpdateStrategy",
|
|
45
|
+
"H3cUpdateStrategy",
|
|
46
|
+
"NettrixUpdateStrategy",
|
|
47
|
+
"XFusionUpdateStrategy",
|
|
48
|
+
"LenovoUpdateStrategy",
|
|
49
|
+
"UpdateStrategyRegistry",
|
|
50
|
+
"VendorDetector",
|
|
51
|
+
]
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base update strategy and generic (standard Redfish) fallback implementation.
|
|
3
|
+
|
|
4
|
+
All vendor-specific strategies inherit from BaseUpdateStrategy and override
|
|
5
|
+
the execute() method to build the vendor-appropriate request body.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional
|
|
12
|
+
|
|
13
|
+
from ...models.common import RedfishResponse
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from ...client import RedfishClient
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BaseUpdateStrategy(ABC):
|
|
22
|
+
"""
|
|
23
|
+
Abstract base class for firmware update strategies.
|
|
24
|
+
|
|
25
|
+
Subclasses must implement execute() to construct the vendor-specific
|
|
26
|
+
request body and POST it to the SimpleUpdate action target.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def _discover_action_target(self, client: RedfishClient) -> str:
|
|
30
|
+
"""
|
|
31
|
+
Discover the SimpleUpdate action target URL from the UpdateService.
|
|
32
|
+
|
|
33
|
+
Falls back to the standard path if the target is not found in Actions.
|
|
34
|
+
"""
|
|
35
|
+
update_service = client._get_update_service()
|
|
36
|
+
raw = client._http_client.get_raw(update_service.odata_id)
|
|
37
|
+
actions = raw.get("Actions", {})
|
|
38
|
+
simple_update_action = actions.get("#UpdateService.SimpleUpdate", {})
|
|
39
|
+
target = simple_update_action.get("target")
|
|
40
|
+
|
|
41
|
+
if not target:
|
|
42
|
+
target = f"{update_service.odata_id}/Actions/UpdateService.SimpleUpdate"
|
|
43
|
+
|
|
44
|
+
return target
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def execute(
|
|
48
|
+
self,
|
|
49
|
+
client: RedfishClient,
|
|
50
|
+
image_uri: str,
|
|
51
|
+
transfer_protocol: str = "HTTP",
|
|
52
|
+
targets: Optional[list] = None,
|
|
53
|
+
**kwargs: Any,
|
|
54
|
+
) -> RedfishResponse:
|
|
55
|
+
"""
|
|
56
|
+
Execute the firmware update.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
client: RedfishClient instance (provides HTTP access)
|
|
60
|
+
image_uri: URI of the firmware image
|
|
61
|
+
transfer_protocol: Transfer protocol (HTTP, NFS, TFTP, etc.)
|
|
62
|
+
targets: Optional list of firmware target paths
|
|
63
|
+
**kwargs: Vendor-specific parameters
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
RedfishResponse (may contain a task reference for async update)
|
|
67
|
+
"""
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class GenericUpdateStrategy(BaseUpdateStrategy):
|
|
72
|
+
"""
|
|
73
|
+
Standard Redfish SimpleUpdate strategy (fallback).
|
|
74
|
+
|
|
75
|
+
Sends the standard body with ImageURI, TransferProtocol, and optional
|
|
76
|
+
Targets. This is equivalent to the original simple_update implementation
|
|
77
|
+
and is used when the vendor is not recognized.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def execute(
|
|
81
|
+
self,
|
|
82
|
+
client: RedfishClient,
|
|
83
|
+
image_uri: str,
|
|
84
|
+
transfer_protocol: str = "HTTP",
|
|
85
|
+
targets: Optional[list] = None,
|
|
86
|
+
**kwargs: Any,
|
|
87
|
+
) -> RedfishResponse:
|
|
88
|
+
target = self._discover_action_target(client)
|
|
89
|
+
|
|
90
|
+
body: Dict[str, Any] = {
|
|
91
|
+
"ImageURI": image_uri,
|
|
92
|
+
"TransferProtocol": transfer_protocol,
|
|
93
|
+
}
|
|
94
|
+
if targets:
|
|
95
|
+
body["Targets"] = targets
|
|
96
|
+
|
|
97
|
+
logger.info(
|
|
98
|
+
"GenericUpdateStrategy: triggering SimpleUpdate with ImageURI=%s",
|
|
99
|
+
image_uri,
|
|
100
|
+
)
|
|
101
|
+
return client._http_client.post(target, RedfishResponse, raw_body=body)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
H3C (新华三) firmware update strategy.
|
|
3
|
+
|
|
4
|
+
H3C BMCs do NOT send a separate TransferProtocol field; the protocol is
|
|
5
|
+
embedded in the ImageURI. For SFTP/CIFS, credentials are also embedded
|
|
6
|
+
in the URI (e.g., sftp://user:pwd@host/path).
|
|
7
|
+
|
|
8
|
+
Reference: UpdateService固件刷新接口 — 新华三
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional
|
|
14
|
+
from urllib.parse import urlparse, urlunparse
|
|
15
|
+
|
|
16
|
+
from ...models.common import RedfishResponse
|
|
17
|
+
from .base import BaseUpdateStrategy
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from ...client import RedfishClient
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class H3cUpdateStrategy(BaseUpdateStrategy):
|
|
26
|
+
"""
|
|
27
|
+
Firmware update strategy for H3C (新华三) servers.
|
|
28
|
+
|
|
29
|
+
Supported kwargs:
|
|
30
|
+
username (str): File server username -> embedded in ImageURI
|
|
31
|
+
password (str): File server password -> embedded in ImageURI
|
|
32
|
+
preserve_config (bool): True -> "Retain", False -> "Restore"
|
|
33
|
+
Maps to Oem.Public.Preserve
|
|
34
|
+
restore_mode (str): Retain/Restore/ForceRestore -> Oem.Public.Preserve
|
|
35
|
+
(overrides preserve_config if both are set)
|
|
36
|
+
reboot_mode (str): Auto/Manual -> Oem.Public.RebootMode
|
|
37
|
+
backup (str): Backup option -> Oem.Public.Backup
|
|
38
|
+
bios_flash (str): Flash1/Flash2/Both -> Oem.Public.BiosFlash
|
|
39
|
+
image_md5_uri (str): MD5 checksum URI -> Oem.Public.ImageMd5URI
|
|
40
|
+
delay (str): Delay duration -> Oem.Public.Date
|
|
41
|
+
upgrade_type (str): all/bios/me/microcode -> Oem.Public.UpgradeType
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def execute(
|
|
45
|
+
self,
|
|
46
|
+
client: RedfishClient,
|
|
47
|
+
image_uri: str,
|
|
48
|
+
transfer_protocol: str = "HTTP",
|
|
49
|
+
targets: Optional[list] = None,
|
|
50
|
+
**kwargs: Any,
|
|
51
|
+
) -> RedfishResponse:
|
|
52
|
+
target = self._discover_action_target(client)
|
|
53
|
+
|
|
54
|
+
# H3C embeds credentials in the URI for SFTP/CIFS
|
|
55
|
+
final_uri = self._build_uri_with_credentials(
|
|
56
|
+
image_uri,
|
|
57
|
+
transfer_protocol,
|
|
58
|
+
kwargs.get("username"),
|
|
59
|
+
kwargs.get("password"),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# H3C does NOT send TransferProtocol as a separate field
|
|
63
|
+
body: Dict[str, Any] = {
|
|
64
|
+
"ImageURI": final_uri,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Build OEM extension
|
|
68
|
+
oem_public: Dict[str, Any] = {}
|
|
69
|
+
|
|
70
|
+
# Preserve config: restore_mode takes precedence
|
|
71
|
+
if kwargs.get("restore_mode"):
|
|
72
|
+
oem_public["Preserve"] = kwargs["restore_mode"]
|
|
73
|
+
elif "preserve_config" in kwargs:
|
|
74
|
+
oem_public["Preserve"] = "Retain" if kwargs["preserve_config"] else "Restore"
|
|
75
|
+
|
|
76
|
+
if kwargs.get("reboot_mode"):
|
|
77
|
+
oem_public["RebootMode"] = kwargs["reboot_mode"]
|
|
78
|
+
if kwargs.get("backup"):
|
|
79
|
+
oem_public["Backup"] = kwargs["backup"]
|
|
80
|
+
if kwargs.get("bios_flash"):
|
|
81
|
+
oem_public["BiosFlash"] = kwargs["bios_flash"]
|
|
82
|
+
if kwargs.get("image_md5_uri"):
|
|
83
|
+
oem_public["ImageMd5URI"] = kwargs["image_md5_uri"]
|
|
84
|
+
if kwargs.get("delay"):
|
|
85
|
+
oem_public["Date"] = kwargs["delay"]
|
|
86
|
+
if kwargs.get("upgrade_type"):
|
|
87
|
+
oem_public["UpgradeType"] = kwargs["upgrade_type"]
|
|
88
|
+
|
|
89
|
+
if oem_public:
|
|
90
|
+
body["Oem"] = {"Public": oem_public}
|
|
91
|
+
|
|
92
|
+
logger.info(
|
|
93
|
+
"H3cUpdateStrategy: triggering SimpleUpdate with ImageURI=%s",
|
|
94
|
+
final_uri,
|
|
95
|
+
)
|
|
96
|
+
return client._http_client.post(target, RedfishResponse, raw_body=body)
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def _build_uri_with_credentials(
|
|
100
|
+
image_uri: str,
|
|
101
|
+
transfer_protocol: str,
|
|
102
|
+
username: Optional[str],
|
|
103
|
+
password: Optional[str],
|
|
104
|
+
) -> str:
|
|
105
|
+
"""
|
|
106
|
+
Embed credentials into the URI for protocols that require it.
|
|
107
|
+
|
|
108
|
+
For SFTP/CIFS, H3C expects: sftp://user:pwd@host/path
|
|
109
|
+
For HTTP/TFTP/NFS, credentials are not embedded.
|
|
110
|
+
"""
|
|
111
|
+
if not username:
|
|
112
|
+
return image_uri
|
|
113
|
+
|
|
114
|
+
proto_lower = transfer_protocol.lower()
|
|
115
|
+
if proto_lower not in ("sftp", "cifs"):
|
|
116
|
+
return image_uri
|
|
117
|
+
|
|
118
|
+
parsed = urlparse(image_uri)
|
|
119
|
+
|
|
120
|
+
# If credentials are already in the URI, don't override
|
|
121
|
+
if parsed.username:
|
|
122
|
+
return image_uri
|
|
123
|
+
|
|
124
|
+
# Build netloc with credentials
|
|
125
|
+
credentials = username
|
|
126
|
+
if password:
|
|
127
|
+
credentials = f"{username}:{password}"
|
|
128
|
+
|
|
129
|
+
new_netloc = f"{credentials}@{parsed.hostname}"
|
|
130
|
+
if parsed.port:
|
|
131
|
+
new_netloc = f"{new_netloc}:{parsed.port}"
|
|
132
|
+
|
|
133
|
+
return urlunparse((
|
|
134
|
+
parsed.scheme or proto_lower,
|
|
135
|
+
new_netloc,
|
|
136
|
+
parsed.path,
|
|
137
|
+
parsed.params,
|
|
138
|
+
parsed.query,
|
|
139
|
+
parsed.fragment,
|
|
140
|
+
))
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Inspur (浪潮) firmware update strategy.
|
|
3
|
+
|
|
4
|
+
Inspur BMCs use heavy OEM extensions under Oem.Public, including
|
|
5
|
+
FlashItem selection, BIOS update type, PFR options, and file server
|
|
6
|
+
credentials in the request body.
|
|
7
|
+
|
|
8
|
+
Reference: UpdateService固件刷新接口 — 浪潮
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional
|
|
14
|
+
|
|
15
|
+
from ...models.common import RedfishResponse
|
|
16
|
+
from .base import BaseUpdateStrategy
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from ...client import RedfishClient
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class InspurUpdateStrategy(BaseUpdateStrategy):
|
|
25
|
+
"""
|
|
26
|
+
Firmware update strategy for Inspur (浪潮) servers.
|
|
27
|
+
|
|
28
|
+
Supported kwargs:
|
|
29
|
+
username (str): File server username -> body.Username
|
|
30
|
+
password (str): File server password -> body.Password
|
|
31
|
+
preserve_config (bool): Preserve config -> Oem.Public.PreserveConf
|
|
32
|
+
flash_item (str): BMC/BIOS/CPLD -> Oem.Public.FlashItem
|
|
33
|
+
bios_update_type (str): FullBIOS/SeamlessBIOS -> Oem.Public.BIOSUpdateType
|
|
34
|
+
bios_flash (str): Flash1/Flash2/Both -> Oem.Public.BiosFlash
|
|
35
|
+
pfr_type (bool): PFR update toggle -> Oem.Public.PFRType
|
|
36
|
+
pfr_region (str): PFR region -> Oem.Public.PFRRegion
|
|
37
|
+
pfr_update_dynamic (bool): PFR dynamic update -> Oem.Public.PFRUpdateDynamic
|
|
38
|
+
seamless_module (str): BIOS_ONLY/ME/MICROCODE -> Oem.Public.SeamlessModule
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def execute(
|
|
42
|
+
self,
|
|
43
|
+
client: RedfishClient,
|
|
44
|
+
image_uri: str,
|
|
45
|
+
transfer_protocol: str = "HTTPS",
|
|
46
|
+
targets: Optional[list] = None,
|
|
47
|
+
**kwargs: Any,
|
|
48
|
+
) -> RedfishResponse:
|
|
49
|
+
target = self._discover_action_target(client)
|
|
50
|
+
|
|
51
|
+
body: Dict[str, Any] = {
|
|
52
|
+
"ImageURI": image_uri,
|
|
53
|
+
"TransferProtocol": transfer_protocol,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# File server credentials
|
|
57
|
+
if kwargs.get("username"):
|
|
58
|
+
body["Username"] = kwargs["username"]
|
|
59
|
+
if kwargs.get("password"):
|
|
60
|
+
body["Password"] = kwargs["password"]
|
|
61
|
+
|
|
62
|
+
# Build OEM extension
|
|
63
|
+
oem_public: Dict[str, Any] = {}
|
|
64
|
+
|
|
65
|
+
if kwargs.get("flash_item"):
|
|
66
|
+
oem_public["FlashItem"] = kwargs["flash_item"]
|
|
67
|
+
if "preserve_config" in kwargs:
|
|
68
|
+
oem_public["PreserveConf"] = bool(kwargs["preserve_config"])
|
|
69
|
+
if kwargs.get("bios_update_type"):
|
|
70
|
+
oem_public["BIOSUpdateType"] = kwargs["bios_update_type"]
|
|
71
|
+
if kwargs.get("bios_flash"):
|
|
72
|
+
oem_public["BiosFlash"] = kwargs["bios_flash"]
|
|
73
|
+
if "pfr_type" in kwargs:
|
|
74
|
+
oem_public["PFRType"] = bool(kwargs["pfr_type"])
|
|
75
|
+
if kwargs.get("pfr_region"):
|
|
76
|
+
oem_public["PFRRegion"] = kwargs["pfr_region"]
|
|
77
|
+
if "pfr_update_dynamic" in kwargs:
|
|
78
|
+
oem_public["PFRUpdateDynamic"] = bool(kwargs["pfr_update_dynamic"])
|
|
79
|
+
if kwargs.get("seamless_module"):
|
|
80
|
+
oem_public["SeamlessModule"] = kwargs["seamless_module"]
|
|
81
|
+
|
|
82
|
+
if oem_public:
|
|
83
|
+
body["Oem"] = {"Public": oem_public}
|
|
84
|
+
|
|
85
|
+
logger.info(
|
|
86
|
+
"InspurUpdateStrategy: triggering SimpleUpdate with ImageURI=%s",
|
|
87
|
+
image_uri,
|
|
88
|
+
)
|
|
89
|
+
return client._http_client.post(target, RedfishResponse, raw_body=body)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lenovo (联想) firmware update strategy.
|
|
3
|
+
|
|
4
|
+
Lenovo BMCs are closest to standard Redfish with no OEM extensions.
|
|
5
|
+
The only addition is optional Username/Password for file server auth.
|
|
6
|
+
|
|
7
|
+
Reference: UpdateService固件刷新接口 — 联想
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional
|
|
13
|
+
|
|
14
|
+
from ...models.common import RedfishResponse
|
|
15
|
+
from .base import BaseUpdateStrategy
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from ...client import RedfishClient
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class LenovoUpdateStrategy(BaseUpdateStrategy):
|
|
24
|
+
"""
|
|
25
|
+
Firmware update strategy for Lenovo (联想) servers.
|
|
26
|
+
|
|
27
|
+
Supported kwargs:
|
|
28
|
+
username (str): File server username -> body.Username
|
|
29
|
+
password (str): File server password -> body.Password
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def execute(
|
|
33
|
+
self,
|
|
34
|
+
client: RedfishClient,
|
|
35
|
+
image_uri: str,
|
|
36
|
+
transfer_protocol: str = "HTTPS",
|
|
37
|
+
targets: Optional[list] = None,
|
|
38
|
+
**kwargs: Any,
|
|
39
|
+
) -> RedfishResponse:
|
|
40
|
+
target = self._discover_action_target(client)
|
|
41
|
+
|
|
42
|
+
body: Dict[str, Any] = {
|
|
43
|
+
"ImageURI": image_uri,
|
|
44
|
+
"TransferProtocol": transfer_protocol,
|
|
45
|
+
}
|
|
46
|
+
if targets:
|
|
47
|
+
body["Targets"] = targets
|
|
48
|
+
|
|
49
|
+
# Lenovo supports file server credentials
|
|
50
|
+
if kwargs.get("username"):
|
|
51
|
+
body["Username"] = kwargs["username"]
|
|
52
|
+
if kwargs.get("password"):
|
|
53
|
+
body["Password"] = kwargs["password"]
|
|
54
|
+
|
|
55
|
+
logger.info(
|
|
56
|
+
"LenovoUpdateStrategy: triggering SimpleUpdate with ImageURI=%s",
|
|
57
|
+
image_uri,
|
|
58
|
+
)
|
|
59
|
+
return client._http_client.post(target, RedfishResponse, raw_body=body)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Nettrix (宁畅) firmware update strategy.
|
|
3
|
+
|
|
4
|
+
Nettrix BMCs are closest to standard Redfish. The only extension is
|
|
5
|
+
SaveConfig at the body top level (not under Oem).
|
|
6
|
+
|
|
7
|
+
Reference: UpdateService固件刷新接口 — 宁畅
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional
|
|
13
|
+
|
|
14
|
+
from ...models.common import RedfishResponse
|
|
15
|
+
from .base import BaseUpdateStrategy
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from ...client import RedfishClient
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class NettrixUpdateStrategy(BaseUpdateStrategy):
|
|
24
|
+
"""
|
|
25
|
+
Firmware update strategy for Nettrix (宁畅) servers.
|
|
26
|
+
|
|
27
|
+
Supported kwargs:
|
|
28
|
+
preserve_config (bool): Preserve config -> body.SaveConfig
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def execute(
|
|
32
|
+
self,
|
|
33
|
+
client: RedfishClient,
|
|
34
|
+
image_uri: str,
|
|
35
|
+
transfer_protocol: str = "HTTPS",
|
|
36
|
+
targets: Optional[list] = None,
|
|
37
|
+
**kwargs: Any,
|
|
38
|
+
) -> RedfishResponse:
|
|
39
|
+
target = self._discover_action_target(client)
|
|
40
|
+
|
|
41
|
+
body: Dict[str, Any] = {
|
|
42
|
+
"ImageURI": image_uri,
|
|
43
|
+
"TransferProtocol": transfer_protocol,
|
|
44
|
+
}
|
|
45
|
+
if targets:
|
|
46
|
+
body["Targets"] = targets
|
|
47
|
+
|
|
48
|
+
# Nettrix puts SaveConfig at the body top level
|
|
49
|
+
if "preserve_config" in kwargs:
|
|
50
|
+
body["SaveConfig"] = bool(kwargs["preserve_config"])
|
|
51
|
+
|
|
52
|
+
logger.info(
|
|
53
|
+
"NettrixUpdateStrategy: triggering SimpleUpdate with ImageURI=%s",
|
|
54
|
+
image_uri,
|
|
55
|
+
)
|
|
56
|
+
return client._http_client.post(target, RedfishResponse, raw_body=body)
|