From 730296f83f51357802106d425404739312994753 Mon Sep 17 00:00:00 2001 From: Abhishek jaiswal Date: Mon, 25 Aug 2025 08:46:22 -0400 Subject: [PATCH] Add BIOS keywords to manage bmc via Redfish API Implements BiosKeywords class to provide BIOS management capabilities for StarlingX test automation. The keywords support connecting to server BMCs using the Redfish API standard for hardware management. Key features: - Automatic system discovery and connection via BMC IP - Boot order configuration with override settings - System information retrieval (vendor, model, system ID) - Support for common boot devices (PXE, HDD, CD, USB, BIOS setup) The implementation uses the python-redfish library to communicate with BMC endpoints. Usage: lab_config = ConfigurationManager.get_lab_config() bm_password = lab_config.get_bm_password() node = lab_config.get_node(host_name) bm_ip = node.get_bm_ip() bm_username = node.get_bm_username() bios_keywords = BiosKeywords(bmc_ip=bm_ip, username=bm_username, password=bm_password) print(bios_keywords.get_boot_order()) bios_keywords.set_boot_order() print(bios_keywords.get_boot_order()) Test Plan: - Manual testing with Dell PowerEdge servers - Verified boot order changes via BMC interface - Tested system discovery and connection establishment Change-Id: I6efc4eec136f67730d14b225fe7a78b318c12eb8 Signed-off-by: Abhishek jaiswal --- Pipfile | 1 + Pipfile.lock | 8 +++ keywords/server/bios_keywords.py | 101 +++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 keywords/server/bios_keywords.py diff --git a/Pipfile b/Pipfile index 943d01b4..975fad5f 100644 --- a/Pipfile +++ b/Pipfile @@ -33,3 +33,4 @@ sphinx = "==8.1.3" sphinx-autobuild = "==2024.2.4" openstackdocstheme = "==3.4.1" reno = "==4.1.0" +python-redfish = "==0.4.4" diff --git a/Pipfile.lock b/Pipfile.lock index 018b2bfe..56613a95 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -865,6 +865,14 @@ "markers": "python_version >= '3.8'", "version": "==8.1.1" }, + "python-redfish": { + "hashes": [ + "sha256:6c37652481dcd5b391e3741234b34f80ece3bcb62b07d9169f6ee3d6495c9e2f", + "sha256:ef5fbbf62cd8d474075c9844a369182c4e1b10dac2e7d4bd461f52c6f1e89ce8" + ], + "index": "pypi", + "version": "==0.4.4" + }, "pyyaml": { "hashes": [ "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", diff --git a/keywords/server/bios_keywords.py b/keywords/server/bios_keywords.py new file mode 100644 index 00000000..844e9e39 --- /dev/null +++ b/keywords/server/bios_keywords.py @@ -0,0 +1,101 @@ +import redfish + +from framework.logging.automation_logger import get_logger +from keywords.base_keyword import BaseKeyword + + +class BiosKeywords(BaseKeyword): + """Keywords for BIOS operations using Redfish API.""" + + def __init__(self, bmc_ip: str, username: str, password: str): + """Initialize Redfish client for Bios keywords. + + Args: + bmc_ip (str): BMC IP address. + username (str): Username for authentication. + password (str): Password for authentication. + """ + self.bmc_ip = bmc_ip + self.username = username + self.password = password + self.system_id = None + self.vendor = None + self.model = None + + self.redfish_client = redfish.redfish_client( + base_url=f"https://{self.bmc_ip}", + username=self.username, + password=self.password, + timeout=30, + default_prefix="/redfish/v1" + ) + self.redfish_client.login(auth='session') + self.discover_system_info() + + def discover_system_info(self): + """Discover system ID from Redfish API.""" + # Detect system ID + systems_resp = self.redfish_client.get("/redfish/v1/Systems") + + if systems_resp.status != 200: + raise Exception(f"Failed to get systems: {systems_resp.status}") + + members = systems_resp.dict.get("Members", []) + if not members: + raise Exception("No system members found in Redfish response") + self.system_id = list(members[0].values())[0] # e.g. /redfish/v1/Systems/System.Embedded.1 + + # Get system details + sys_info = self.get_system_info() + self.vendor = sys_info.get("Manufacturer", "Unknown") + self.model = sys_info.get("Model", "Unknown") + + get_logger().log_info(f"Connected to {self.vendor} {self.model}, System ID: {self.system_id}") + + def get_system_info(self): + """Fetch system information""" + if not self.system_id: + raise Exception("Not connected to system") + + resp = self.redfish_client.get(self.system_id) + if resp.status == 200: + # get_logger().log_info(f"system_info {resp.dict}") + return resp.dict + else: + raise Exception(f"Failed to fetch system info: {resp.status}") + + def get_boot_order(self): + """Fetch current boot order and override settings""" + if not self.system_id: + raise Exception("Not connected to system") + + resp = self.redfish_client.get(self.system_id) + if resp.status == 200: + return resp.dict.get("Boot", {}) + else: + raise Exception(f"Failed to fetch system info: {resp.status}") + + return self.boot_order + + def set_boot_order(self, device="Pxe", enabled="Once"): + """Set boot order (override target device). + + :param device (str): Boot device (e.g., "Pxe", "Hdd", "Cd", "Usb", "BiosSetup") + :param enabled (str): Boot override enabled mode ("Once", "Continuous", "Disabled") + """ + if not self.system_id: + raise Exception("Not connected to system") + + payload = { + "Boot": { + "BootSourceOverrideTarget": device, + "BootSourceOverrideEnabled": enabled + } + } + + resp = self.redfish_client.patch(self.system_id, body=payload) + + if resp.status in [200, 204]: + get_logger().log_info(f"Boot device set to {device}, mode={enabled}") + else: + raise Exception(f"Failed to set boot order: {resp.status}, {resp.text}") \ No newline at end of file