Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ docs/workflows/
# devbox's envrc
.envrc
.direnv
.vscode

# mkdocs site output
/site/
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"@odata.context": "/redfish/v1/$metadata#ManagerCollection.ManagerCollection",
"@odata.id": "/redfish/v1/Managers",
"@odata.type": "#ManagerCollection.ManagerCollection",
"Description": "BMC",
"Members": [
{
"@odata.id": "/redfish/v1/Managers/iDRAC.Embedded.1"
}
],
"Members@odata.count": 1,
"Name": "Manager"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"@odata.context": "/redfish/v1/$metadata#EthernetInterfaceCollection.EthernetInterfaceCollection",
"@odata.id": "/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces",
"@odata.type": "#EthernetInterfaceCollection.EthernetInterfaceCollection",
"Description": "Collection of EthernetInterfaces for this Manager",
"Members": [
{
"@odata.id": "/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces/NIC.1"
}
],
"Members@odata.count": 1,
"Name": "Ethernet Network Interface Collection"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"@odata.context": "/redfish/v1/$metadata#ComputerSystemCollection.ComputerSystemCollection",
"@odata.id": "/redfish/v1/Systems",
"@odata.type": "#ComputerSystemCollection.ComputerSystemCollection",
"Description": "Collection of Computer Systems",
"Members": [
{
"@odata.id": "/redfish/v1/Systems/System.Embedded.1"
}
],
"Members@odata.count": 1,
"Name": "Computer System Collection"
}
1 change: 1 addition & 0 deletions python/understack-workflows/tests/test_bmc_chassis_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class FakeBmc(Bmc):
def __init__(self, fixtures):
self.fixtures = fixtures
self.ip_address = "1.2.3.4"
super().__init__(ip_address=self.ip_address)

def redfish_request(self, path: str, *_args, **_kw) -> dict:
path = path.replace("/", "_") + ".json"
Expand Down
55 changes: 44 additions & 11 deletions python/understack-workflows/understack_workflows/bmc.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# pylint: disable=E1131,C0103

import logging
import os
from dataclasses import dataclass
Expand All @@ -18,21 +20,54 @@
}


class RedfishRequestError(Exception):
"""Handle Exceptions from Redfish handler."""


@dataclass
class Bmc:
"""Represent DRAC/iLo and know how to perform low-level query on it."""

ip_address: str
username: str
password: str
def __init__(
self, ip_address: str, password: str | None = None, username: str = "root"
) -> None:
"""Initialize BMC data class."""
self.ip_address = ip_address
self.username = username
self.password = password if password else ""
self._system_path: str | None = None
self._manager_path: str | None = None

@property
def system_path(self) -> str:
"""Read System path from BMC."""
self._system_path = self._system_path or self.get_system_path()
return self._system_path

@property
def manager_path(self) -> str:
"""Read Manager path from BMC."""
self._manager_path = self._manager_path or self.get_manager_path()
return self._manager_path

def __str__(self):
"""Stringify without password being printed."""
return f"BMC {self.url()}"

def url(self):
"""Return base redfish URL."""
return f"https://{self.ip_address}"

def get_system_path(self):
"""Get System Path."""
_result = self.redfish_request("/redfish/v1/Systems/")
return _result["Members"][0]["@odata.id"].rstrip("/")

def get_manager_path(self):
"""Get Manager Path."""
_result = self.redfish_request("/redfish/v1/Managers/")
return _result["Members"][0]["@odata.id"].rstrip("/")

def redfish_request(
self,
path: str,
Expand All @@ -41,6 +76,7 @@ def redfish_request(
verify: bool = False,
timeout: int = 30,
) -> dict:
"""Request a path via Redfish against the Bmc."""
url = f"{self.url()}{path}"
r = requests.request(
method,
Expand All @@ -52,16 +88,17 @@ def redfish_request(
headers=HEADERS,
)
if r.status_code >= 400:
raise RedfishError(
raise RedfishRequestError(
f"BMC communications failure HTTP {r.status_code} "
f"{r.reason} from {url} - {r.text}"
+ f"{r.reason} from {url} - {r.text}"
)
if r.text:
return r.json()
else:
return {}

def sushy(self):
"""Return a Sushy interface to BMC."""
return Sushy(
self.url(), username=self.username, password=self.password, verify=False
)
Expand All @@ -72,8 +109,8 @@ def bmc_for_ip_address(
) -> Bmc:
"""Factory method to create a Bmc object with a standard password.

If no password is supplied then we use the conventional BMC standard
password which is derived from the IP address and the BMC_MASTER secret key.
If no password is supplied then we use a conventional BMC standard one
which is derived from the IP address and the BMC_MASTER secret key.

If no username is supplied then the username "root" is used.
"""
Expand All @@ -86,7 +123,3 @@ def bmc_for_ip_address(
password=password,
username=username,
)


class RedfishError(Exception):
pass
18 changes: 9 additions & 9 deletions python/understack-workflows/understack_workflows/bmc_bios.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
from understack_workflows.bmc import Bmc
from understack_workflows.bmc import RedfishError
from understack_workflows.bmc import RedfishRequestError
from understack_workflows.helpers import setup_logger

logger = setup_logger(__name__)

REDFISH_BIOS_PATH = "/redfish/v1/Systems/System.Embedded.1/Bios"


def required_bios_settings(pxe_interface: str) -> dict:
"""Return adjusted Bios settings map for BMC."""
return {
"PxeDev1EnDis": "Enabled",
"PxeDev1Interface": pxe_interface,
Expand All @@ -27,7 +26,7 @@ def update_dell_bios_settings(bmc: Bmc, pxe_interface="NIC.Integrated.1-1") -> d

Returns the changes that were made
"""
current_settings = bmc.redfish_request(REDFISH_BIOS_PATH)["Attributes"]
current_settings = bmc.redfish_request(bmc.system_path + "/Bios")["Attributes"]
required_settings = required_bios_settings(pxe_interface)

required_changes = {
Expand All @@ -39,22 +38,23 @@ def update_dell_bios_settings(bmc: Bmc, pxe_interface="NIC.Integrated.1-1") -> d
if required_changes:
logger.info("%s Updating BIOS settings: %s", bmc, required_changes)
patch_bios_settings(bmc, required_changes)
logger.info("%s BIOS settings will be updated on next server boot", bmc)
logger.info("%s BIOS settings will be updated on next server boot.", bmc)
else:
logger.info("%s all required BIOS settings present and correct", bmc)
logger.info("%s all required BIOS settings present and correct.", bmc)

return required_changes


def patch_bios_settings(bmc: Bmc, new_settings: dict):
path = f"{REDFISH_BIOS_PATH}/Settings"
"""Apply Bios settings to BMC."""
settings_path = f"{bmc.system_path}/Bios/Settings"
payload = {
"@Redfish.SettingsApplyTime": {"ApplyTime": "OnReset"},
"Attributes": new_settings,
}
try:
bmc.redfish_request(path, payload=payload, method="PATCH")
except RedfishError as e:
bmc.redfish_request(settings_path, payload=payload, method="PATCH")
except RedfishRequestError as e:
if "Pending configuration values" in repr(e):
logger.info("%s BIOS settings job already queued, ignoring.", bmc)
return
Expand Down
Loading
Loading