Files

1304 lines
47 KiB
Python

###
# Copyright 2020 Hewlett Packard Enterprise, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###
# -*- coding: utf-8 -*-
"""Base implementation for interaction with blob store interface"""
# ---------Imports---------
import os
import sys
import platform
import sysconfig
import logging
import random
import struct
import string
import time
import ctypes
from ctypes import POINTER, c_char_p, c_ubyte, c_uint, c_ushort, c_void_p, create_string_buffer
from redfish.hpilo.rishpilo import BlobReturnCodes as hpiloreturncodes
from redfish.hpilo.rishpilo import (
HpIlo,
HpIloChifPacketExchangeError,
HpIloInitialError,
)
if os.name == "nt":
from ctypes import windll
else:
from _ctypes import dlclose
# Flags from <dlfcn.h> (Linux/Unix)
RTLD_LAZY = 0x00001
RTLD_GLOBAL = 0x00100
# ---------End of imports---------
# ---------Debug logger---------
LOGGER = logging.getLogger(__name__)
# ---------End of debug logger---------
# -----------------------Error Returns----------------------
class UnexpectedResponseError(Exception):
"""Raise when we get data that we don't expect from iLO"""
pass
class HpIloError(Exception):
"""Raised when iLO returns non-zero error code"""
pass
class Blob2CreateError(Exception):
"""Raised when create operation fails"""
pass
class Blob2InfoError(Exception):
"""Raised when create operation fails"""
pass
class Blob2ReadError(Exception):
"""Raised when read operation fails"""
pass
class Blob2WriteError(Exception):
"""Raised when write operation fails"""
pass
class Blob2DeleteError(Exception):
"""Raised when delete operation fails"""
pass
class Blob2OverrideError(Exception):
"""Raised when delete operation fails because of it been overwritten"""
pass
class BlobRetriesExhaustedError(Exception):
"""Raised when max retries have been attempted for same operation"""
pass
class Blob2FinalizeError(Exception):
"""Raised when finalize operation fails"""
pass
class Blob2ListError(Exception):
"""Raised when list operation fails"""
pass
class Blob2SecurityError(Exception):
"""Raised when there is an issue with security"""
pass
class BlobNotFoundError(Exception):
"""Raised when blob not found in key/namespace"""
pass
class ChifDllMissingError(Exception):
"""Raised when unable to obtain ilorest_chif dll handle"""
pass
class EncryptionEnabledError(Exception):
"""Raised when high security encryption is enabled"""
pass
# ----------------------------------------------------------
# -------------------Helper functions-------------------------
class BlobReturnCodes(object):
"""Blob store return codes.
SUCCESS success
BADPARAMETER bad parameter supplied
NOTFOUND blob name not found
NOTMODIFIED call did not perform the operation
"""
SUCCESS = 0
BADPARAMETER = 2
NOTFOUND = 12
NOTMODIFIED = 20
class BlobStore2(object):
"""Blob store 2 class"""
def __init__(self, log_dir=None):
lib = self.gethprestchifhandle()
self.log_dir = log_dir
self.channel = HpIlo(dll=lib, log_dir=log_dir)
self.max_retries = 44
self.max_read_retries = 3
self.delay = 0.25
LOGGER.info("BlobStore initialized with log directory: %s", log_dir)
def __del__(self):
"""Blob store 2 close channel function"""
if hasattr(self, "channel"):
self.channel.close()
def create(self, key, namespace):
"""
Create the blob.
:param key: The blob key to create.
:type key: str
:param namespace: The blob namespace to create the key in.
:type namespace: str
:return: The response from the blob creation request.
:rtype: bytearray
:raises HpIloError: If the operation fails.
"""
LOGGER.info("Attempting to create blob: Key=%s, Namespace=%s", key, namespace)
try:
lib = self.gethprestchifhandle()
lib.create_not_blobentry.argtypes = [c_char_p, c_char_p]
lib.create_not_blobentry.restype = POINTER(c_ubyte)
name = create_string_buffer(key.encode("utf-8"))
namespace = create_string_buffer(namespace.encode("utf-8"))
LOGGER.debug("Calling create_not_blobentry()")
ptr = lib.create_not_blobentry(name, namespace)
LOGGER.debug("Preparing request data")
data = ptr[: lib.size_of_createRequest()]
data = bytearray(data)
LOGGER.info("Sending blob creation request")
resp = self._send_receive_raw(data)
errorcode = struct.unpack("<I", bytes(resp[8:12]))[0]
if not (errorcode == BlobReturnCodes.SUCCESS or errorcode == BlobReturnCodes.NOTMODIFIED):
LOGGER.error("Blob creation failed. Error Code: %d", errorcode)
raise HpIloError(errorcode)
LOGGER.info("Blob creation successful for Key=%s, Namespace=%s", key, namespace)
return resp
except Exception as e:
LOGGER.exception("Exception during blob creation: %s", str(e))
raise
finally:
LOGGER.debug("Unloading library handle")
self.unloadchifhandle(lib)
def get_info(self, key, namespace, retries=0):
"""Get information for a particular blob.
:param key: The blob key to retrieve.
:type key: str.
:param namespace: The blob namespace to retrieve the key from.
:type namespace: str.
"""
LOGGER.info(f"get_info called with key='{key}', namespace='{namespace}', retries={retries}")
lib = self.gethprestchifhandle()
lib.get_info.argtypes = [c_char_p, c_char_p]
lib.get_info.restype = POINTER(c_ubyte)
name = create_string_buffer(key.encode("utf-8"))
namspace = create_string_buffer(namespace.encode("utf-8"))
ptr = lib.get_info(name, namspace)
data = ptr[: lib.size_of_infoRequest()]
data = bytearray(data)
delay = self.delay
while retries <= self.max_retries:
LOGGER.debug(f"Attempt {retries+1}/{self.max_retries} - Sending request to iLO.")
resp = self._send_receive_raw(data)
errorcode = struct.unpack("<I", bytes(resp[8:12]))[0]
header = resp[:8].hex()
LOGGER.debug(f"Response received - Error Code: {errorcode}, Headers: {header}, Size: {len(resp)} bytes")
if errorcode == BlobReturnCodes.BADPARAMETER:
if retries < self.max_retries:
LOGGER.warning(
f"BADPARAMETER error received. Retrying in {delay} seconds... "
f"({retries+1}/{self.max_retries})"
)
time.sleep(delay)
retries += 1
delay += 0.05
continue
else:
LOGGER.error(f"Max retries ({self.max_retries}) exceeded. Raising Blob2OverrideError.")
raise Blob2OverrideError(errorcode)
elif errorcode == BlobReturnCodes.NOTFOUND:
LOGGER.error(f"BlobNotFoundError: key='{key}', namespace='{namespace}'")
raise BlobNotFoundError(key, namespace)
elif errorcode not in (BlobReturnCodes.SUCCESS, BlobReturnCodes.NOTMODIFIED):
LOGGER.error(f"HpIloError: Unexpected error code {errorcode}.")
raise HpIloError(errorcode)
else:
LOGGER.info("Request successful. Extracting response data.")
break
response = resp[lib.size_of_responseHeaderBlob() :]
self.unloadchifhandle(lib)
LOGGER.info(f"get_info completed successfully for key='{key}', namespace='{namespace}'.")
return response
def read(self, key, namespace, retries=0):
"""Read a particular blob
:param key: The blob key to be read.
:type key: str.
:param namespace: The blob namespace to read the key from.
:type namespace: str.
"""
LOGGER.info("Starting blob read: Key=%s, Namespace=%s, Retry=%d", key, namespace, retries)
try:
lib = self.gethprestchifhandle()
maxread = lib.max_read_size()
readsize = lib.size_of_readRequest()
readhead = lib.size_of_responseHeaderBlob()
LOGGER.debug(f"Read parameters: maxread={maxread}, readsize={readsize}, readhead={readhead}")
self.unloadchifhandle(lib)
# Get blob metadata
LOGGER.debug("Fetching blob info to determine size.")
blob_info = self.get_info(key, namespace)
blobsize = struct.unpack("<I", bytes(blob_info[0:4]))[0]
LOGGER.info("Blob size determined: %d bytes", blobsize)
bytes_read = 0
data = bytearray()
while bytes_read < blobsize:
count = min(maxread - readsize, blobsize - bytes_read)
LOGGER.debug("Reading fragment: Offset=%d, Size=%d", bytes_read, count)
recvpkt = self.read_fragment(key, namespace, bytes_read, count)
newreadsize = readhead + 4
bytesread = struct.unpack("<I", bytes(recvpkt[readhead:newreadsize]))[0]
LOGGER.debug("Bytes read in current fragment: %d", bytesread)
if bytesread == 0:
LOGGER.debug(f"Zero bytes read in fragment. Fragment metadata: offset={bytes_read}, count={count}")
if retries < self.max_read_retries:
LOGGER.warning(
"Read attempt failed. Retrying (Attempt %d/%d).", retries + 1, self.max_read_retries
)
return self.read(key=key, namespace=namespace, retries=retries + 1)
else:
LOGGER.error("Maximum read retries (%d) exceeded.", self.max_read_retries)
raise BlobRetriesExhaustedError()
data.extend(recvpkt[newreadsize : newreadsize + bytesread])
bytes_read += bytesread
LOGGER.debug("Total bytes read so far: %d/%d", bytes_read, blobsize)
LOGGER.info("Blob read successfully: Key=%s, Namespace=%s, Total Bytes=%d", key, namespace, len(data))
return data
except Exception as e:
LOGGER.exception("Error during blob read: %s", str(e))
raise
def read_fragment(self, key, namespace, offset=0, count=1):
"""Fragmented version of read function for large blobs
:param key: The blob key to be read.
:type key: str.
:param namespace: The blob namespace to read the key from.
:type namespace: str.
:param offset: The data offset for the current fragmented read.
:type key: int.
:param count: The data count for the current fragmented read.
:type namespace: int.
"""
LOGGER.info(
"Starting fragmented blob read: Key=%s, Namespace=%s, Offset=%d, Count=%d", key, namespace, offset, count
)
try:
lib = self.gethprestchifhandle()
lib.read_fragment.argtypes = [c_uint, c_uint, c_char_p, c_char_p]
lib.read_fragment.restype = POINTER(c_ubyte)
name = create_string_buffer(key.encode("utf-8"))
namespace_buf = create_string_buffer(namespace.encode("utf-8"))
LOGGER.debug("Preparing request data for read_fragment.")
ptr = lib.read_fragment(offset, count, name, namespace_buf)
request_size = lib.size_of_readRequest()
response_size = lib.size_of_readResponse()
LOGGER.debug("Extracting request data (Size=%d)", request_size)
data = bytearray(ptr[:request_size])
LOGGER.info("Sending fragmented read request to iLO.")
resp = self._send_receive_raw(data)
# Check response error code
errorcode = struct.unpack("<I", bytes(resp[8:12]))[0]
LOGGER.debug(
f"Response received - Headers: Status={errorcode}, Size={len(resp)}, Expected Size={response_size}"
)
if len(resp) < response_size:
LOGGER.warning("Received response smaller than expected. Padding response.")
resp = resp + b"\0" * (response_size - len(resp))
LOGGER.info("Fragmented blob read successful: Key=%s, Bytes Received=%d", key, len(resp))
return resp
except Exception as e:
LOGGER.exception("Error occurred during read_fragment: %s", str(e))
raise
def write(self, key, namespace, data=None):
"""Write a particular blob
:param key: The blob key to be written.
:type key: str.
:param namespace: The blob namespace to write the key in.
:type namespace: str.
:param data: The blob data to be written.
:type data: str.
"""
LOGGER.info(f"Starting to write blob with key: {key} in namespace: {namespace}")
lib = self.gethprestchifhandle()
maxwrite = lib.max_write_size()
writesize = lib.size_of_writeRequest()
LOGGER.debug(f"Max write size: {maxwrite}, Write request size: {writesize}")
self.unloadchifhandle(lib)
if data:
data_length = len(data)
bytes_written = 0
LOGGER.info(f"Data length: {data_length}")
while bytes_written < data_length:
if (maxwrite - writesize) < (data_length - bytes_written):
count = maxwrite - writesize
else:
count = data_length - bytes_written
write_blob_size = bytes_written
LOGGER.debug(f"Writing data from byte {write_blob_size} to {write_blob_size + count}")
try:
self.write_fragment(
key,
namespace=namespace,
data=data[write_blob_size : write_blob_size + count],
offset=write_blob_size,
count=count,
)
except Exception as e:
LOGGER.error(f"Failed to write fragment for blob with key {key} in namespace {namespace}: {e}")
raise
bytes_written += count
LOGGER.debug(f"{bytes_written} bytes written so far.")
try:
result = self.finalize(key, namespace=namespace)
LOGGER.info(f"Finalization successful for key: {key} in namespace: {namespace}")
return result
except Exception as e:
LOGGER.error(f"Failed to finalize the write for blob with key {key} in namespace {namespace}: {e}")
raise
def write_fragment(self, key, namespace, data=None, offset=0, count=1):
"""Fragmented version of write function for large blobs
:param key: The blob key to be written.
:type key: str.
:param namespace: The blob namespace to write the key in.
:type namespace: str.
:param data: The blob data to be written to blob.
:type data: str.
:param offset: The data offset for the current fragmented write.
:type key: int.
:param count: The data count for the current fragmented write.
:type count: int.
"""
LOGGER.info(f"Starting fragmented write for blob with key: {key} in namespace: {namespace}")
LOGGER.debug(f"Fragment write params: offset={offset}, count={count}")
lib = self.gethprestchifhandle()
lib.write_fragment.argtypes = [c_uint, c_uint, c_char_p, c_char_p]
lib.write_fragment.restype = POINTER(c_ubyte)
name = create_string_buffer(key.encode("utf-8"))
namespace = create_string_buffer(namespace.encode("utf-8"))
# Call the external library function
try:
LOGGER.debug(f"Write fragment request: key={key}, namespace={namespace}, offset={offset}, count={count}")
ptr = lib.write_fragment(offset, count, name, namespace)
except Exception as e:
LOGGER.error(f"Error while calling write_fragment for key: {key} in namespace: {namespace}: {e}")
raise
LOGGER.debug(f"Preparing write fragment data: request_size={count} bytes")
sendpacket = ptr[: lib.size_of_writeRequest()]
if isinstance(data, str):
data = data.encode("utf-8")
dataarr = bytearray(sendpacket)
dataarr.extend(memoryview(data))
LOGGER.debug(f"Request metadata: offset={offset}, size={count}")
# Send the data
try:
resp = self._send_receive_raw(dataarr)
except Exception as e:
LOGGER.error(f"Error during sending/receiving raw data for blob with key: {key}: {e}")
raise
# Check for errors in response
errorcode = struct.unpack("<I", bytes(resp[8:12]))[0]
if not (errorcode == BlobReturnCodes.SUCCESS or errorcode == BlobReturnCodes.NOTMODIFIED):
LOGGER.error(f"Write fragment failed for key: {key} in namespace: {namespace}. Error code: {errorcode}")
raise HpIloError(errorcode)
LOGGER.info(f"Fragmented write successful for key: {key} in namespace: {namespace}")
self.unloadchifhandle(lib)
return resp
def delete(self, key, namespace, retries=0):
"""Delete the blob.
:param key: The blob key to be deleted.
:type key: str
:param namespace: The blob namespace to delete the key from.
:type namespace: str
:param retries: The number of retries if deletion fails (default 0).
:type retries: int
"""
LOGGER.info(f"Starting blob deletion: key={key}, namespace={namespace}, retries={retries}")
lib = self.gethprestchifhandle()
lib.delete_blob.argtypes = [c_char_p, c_char_p]
lib.delete_blob.restype = POINTER(c_ubyte)
name = create_string_buffer(key.encode("utf-8"))
namspace = create_string_buffer(namespace.encode("utf-8"))
ptr = lib.delete_blob(name, namspace)
data = ptr[: lib.size_of_deleteRequest()]
data = bytearray(data)
delay = self.delay
while retries <= self.max_retries:
LOGGER.debug(f"Attempt {retries + 1} of {self.max_retries}: Sending raw delete request.")
resp = self._send_receive_raw(data)
# Checking for errors in the received response
errorcode = struct.unpack("<I", bytes(resp[8:12]))[0]
LOGGER.debug(f"Received error code: {errorcode}")
if errorcode == BlobReturnCodes.BADPARAMETER:
LOGGER.warning(
f"BADPARAMETER error received (retries left: {self.max_retries - retries}). "
f"Retrying after {delay} seconds."
)
if retries < self.max_retries:
time.sleep(delay)
retries += 1
delay += 0.05
continue
else:
LOGGER.warning(f"Max retries reached. Unable to delete blob key={key}. Raising Blob2OverrideError.")
raise Blob2OverrideError(errorcode)
elif errorcode not in (BlobReturnCodes.SUCCESS, BlobReturnCodes.NOTMODIFIED):
LOGGER.debug(f"Error during blob deletion: {errorcode}. Raising HpIloError.")
raise HpIloError(errorcode)
else:
LOGGER.info(f"Blob deletion successful for key={key} in namespace={namespace}.")
break
self.unloadchifhandle(lib)
LOGGER.info(f"Exiting delete function for key={key}. Final error code: {errorcode}")
return errorcode
def list(self, namespace):
"""List operation to retrieve all blobs in a given namespace.
:param namespace: The blob namespace to retrieve the keys from.
:type namespace: str.
"""
LOGGER.info(f"Starting blob listing operation for namespace: {namespace}")
lib = self.gethprestchifhandle()
lib.list_blob.argtypes = [c_char_p]
lib.list_blob.restype = POINTER(c_ubyte)
namespace = create_string_buffer(namespace.encode("utf-8"))
LOGGER.debug("Sending list blob request to iLO.")
ptr = lib.list_blob(namespace)
data = ptr[: lib.size_of_listRequest()]
data = bytearray(data)
# Send and receive raw data
resp = self._send_receive_raw(data)
# Check for errors in the response
errorcode = struct.unpack("<I", bytes(resp[8:12]))[0]
LOGGER.debug(f"Received error code: {errorcode}")
if not (errorcode == BlobReturnCodes.SUCCESS or errorcode == BlobReturnCodes.NOTMODIFIED):
LOGGER.error(f"Error during blob listing: {errorcode}. Raising HpIloError.")
raise HpIloError(errorcode)
# Padding response to match expected size
resp = resp + b"\0" * (lib.size_of_listResponse() - len(resp))
# Unload the handler
self.unloadchifhandle(lib)
LOGGER.info(f"Blob listing successful for namespace: {namespace}")
return resp
def finalize(self, key, namespace):
"""Finalize the blob.
:param key: The blob key to be finalized.
:type key: str.
:param namespace: The blob namespace to finalize the key in.
:type namespace: str.
"""
LOGGER.info(f"Starting blob finalize operation for key: {key}, namespace: {namespace}")
# Getting the handler library
lib = self.gethprestchifhandle()
lib.finalize_blob.argtypes = [c_char_p, c_char_p]
lib.finalize_blob.restype = POINTER(c_ubyte)
# Preparing request data
name = create_string_buffer(key.encode("utf-8"))
namespace = create_string_buffer(namespace.encode("utf-8"))
# Sending the finalize request
LOGGER.debug("Sending finalize blob request to iLO.")
ptr = lib.finalize_blob(name, namespace)
data = ptr[: lib.size_of_finalizeRequest()]
data = bytearray(data)
# Send and receive raw data
resp = self._send_receive_raw(data)
# Checking for errors in the response
errorcode = struct.unpack("<I", bytes(resp[8:12]))[0]
LOGGER.debug(f"Received error code: {errorcode}")
if not (errorcode == BlobReturnCodes.SUCCESS or errorcode == BlobReturnCodes.NOTMODIFIED):
LOGGER.error(f"Error during blob finalize: {errorcode}. Raising HpIloError.")
raise HpIloError(errorcode)
# Unload the handler
self.unloadchifhandle(lib)
LOGGER.info(f"Blob finalize successful for key: {key}, namespace: {namespace}")
return errorcode
def rest_immediate(
self,
req_data,
rqt_key="RisRequest",
rsp_key="RisResponse",
rsp_namespace="volatile",
):
"""Read/write blob via immediate operation
:param req_data: The blob data to be read/written.
:type req_data: str.
:param rqt_key: The blob key to be used for the request data.
:type rqt_key: str.
:param rsp_key: The blob key to be used for the response data.
:type rsp_key: str.
:param rsp_namespace: The blob namespace to retrieve the response from.
:type rsp_namespace: str.
"""
# Log generated keys
rqt_key = "".join(random.choice(string.ascii_letters + string.digits) for _ in range(10))
rsp_key = "".join(random.choice(string.ascii_letters + string.digits) for _ in range(10))
LOGGER.debug(f"Generated request key: {rqt_key}, response key: {rsp_key}")
LOGGER.debug(f"Request data size: {len(req_data)} bytes")
lib = self.gethprestchifhandle()
# Log request size check
if len(req_data) < (lib.size_of_restImmediateRequest() + lib.max_write_size()):
LOGGER.debug("Proceeding with immediate request (no blob required).")
# Rest immediate operation
lib.rest_immediate.argtypes = [c_uint, c_char_p, c_char_p]
lib.rest_immediate.restype = POINTER(c_ubyte)
name = create_string_buffer(rsp_key.encode("utf-8"))
namespace = create_string_buffer(rsp_namespace.encode("utf-8"))
ptr = lib.rest_immediate(len(req_data), name, namespace)
sendpacket = ptr[: lib.size_of_restImmediateRequest()]
mode = False
else:
# Log that we're handling large data
LOGGER.debug("Data is large, proceeding with create/write operations.")
self.create(rqt_key, rsp_namespace)
self.write(rqt_key, rsp_namespace, req_data)
lib.rest_immediate_blobdesc.argtypes = [c_char_p, c_char_p, c_char_p]
lib.rest_immediate_blobdesc.restype = POINTER(c_ubyte)
name = create_string_buffer(rqt_key.encode("utf-8"))
namespace = create_string_buffer(rsp_namespace.encode("utf-8"))
rspname = create_string_buffer(rsp_key.encode("utf-8"))
ptr = lib.rest_immediate_blobdesc(name, rspname, namespace)
sendpacket = ptr[: lib.size_of_restBlobRequest()]
mode = True
data = bytearray(sendpacket)
if not mode:
data.extend(req_data)
LOGGER.debug("Sending request data to iLO.")
# Sending the data
resp = self._send_receive_raw(data)
# Log response processing
errorcode = struct.unpack("<I", bytes(resp[8:12]))[0]
LOGGER.debug("Received error code: %s", errorcode)
if errorcode == BlobReturnCodes.NOTFOUND:
LOGGER.error("Blob not found for key: %s in namespace: %s", rsp_key, rsp_namespace)
raise BlobNotFoundError(rsp_key, rsp_namespace)
recvmode = struct.unpack("<I", bytes(resp[12:16]))[0]
fixdlen = lib.size_of_restResponseFixed()
response = resp[fixdlen : struct.unpack("<I", bytes(resp[16:20]))[0] + fixdlen]
tmpresponse = None
if errorcode == BlobReturnCodes.SUCCESS and not mode:
if recvmode == 0:
tmpresponse = "".join(map(chr, response))
LOGGER.debug("Successfully received response: %s", tmpresponse)
elif errorcode == BlobReturnCodes.NOTMODIFIED and not mode:
if recvmode == 0:
tmpresponse = "".join(map(chr, response))
LOGGER.debug("No modification detected, response: %s", tmpresponse)
elif errorcode == BlobReturnCodes.SUCCESS:
if recvmode == 0:
tmpresponse = "".join(map(chr, response))
LOGGER.debug("Successfully received response after modification: %s", tmpresponse)
elif recvmode == 0:
LOGGER.error("Error processing response: %s", errorcode)
raise HpIloError(errorcode)
# Cleanup and response handling
self.unloadchifhandle(lib)
if not tmpresponse and recvmode == 1:
tmpresponse = self.read(rsp_key, rsp_namespace)
LOGGER.debug("Fallback response: %s", tmpresponse)
try:
self.delete(rsp_key, rsp_namespace)
LOGGER.debug("Successfully deleted blob with key: %s", rsp_key)
except Exception as excp:
LOGGER.warning("Error deleting blob: %s", excp)
raise excp
else:
try:
self.delete(rsp_key, rsp_namespace)
LOGGER.debug("Successfully deleted blob with key: %s", rsp_key)
except Blob2OverrideError:
LOGGER.warning("Blob delete skipped due to Blob2OverrideError.")
except HpIloChifPacketExchangeError:
LOGGER.warning("Packet exchange error during delete operation.")
except Exception as excp:
LOGGER.warning("Error deleting blob: %s", excp)
raise excp
return tmpresponse
def get_security_state(self):
"""Get information for the current security state"""
LOGGER.debug("Fetching security state from iLO.")
lib = self.gethprestchifhandle()
lib.get_security_state.argtypes = []
lib.get_security_state.restype = POINTER(c_ubyte)
try:
# Fetch the security state
ptr = lib.get_security_state()
data = ptr[: lib.size_of_securityStateRequest()]
data = bytearray(data)
# Send and receive raw data
resp = self._send_receive_raw(data)
# Extract the error code from the response
errorcode = struct.unpack("<I", bytes(resp[8:12]))[0]
# Check for errors and raise exceptions if needed
if not (errorcode == BlobReturnCodes.SUCCESS or errorcode == BlobReturnCodes.NOTMODIFIED):
LOGGER.error("Error occurred with code: %d", errorcode)
raise HpIloError(errorcode)
# Attempt to retrieve the security state from the response
try:
securitystate = struct.unpack("<c", bytes(resp[72]))[0]
LOGGER.debug("Security state extracted as character: %s", securitystate)
except Exception:
# Fallback for non-character extraction (may be an integer)
securitystate = int(resp[72])
LOGGER.debug("Failed to extract character. Security state interpreted as integer: %d", securitystate)
self.unloadchifhandle(lib)
LOGGER.debug("Returning security state: %s", securitystate)
return securitystate
except Exception as e:
LOGGER.error("Error while fetching security state: %s", str(e))
raise
def mount_blackbox(self):
"""Operation to mount the blackbox partition"""
lib = self.gethprestchifhandle()
lib.blackbox_media_mount.argtypes = []
lib.blackbox_media_mount.restype = POINTER(c_ubyte)
ptr = lib.blackbox_media_mount()
data = ptr[: lib.size_of_embeddedMediaRequest()]
data = bytearray(data)
resp = self._send_receive_raw(data)
errorcode = resp[12]
if not (errorcode == BlobReturnCodes.SUCCESS or errorcode == BlobReturnCodes.NOTMODIFIED):
raise HpIloError(errorcode)
self.unloadchifhandle(lib)
return resp
def absaroka_media_mount(self):
"""Operation to mount the absaroka repo partition"""
lib = self.gethprestchifhandle()
lib.absaroka_media_mount.argtypes = []
lib.absaroka_media_mount.restype = POINTER(c_ubyte)
ptr = lib.absaroka_media_mount()
data = ptr[: lib.size_of_embeddedMediaRequest()]
data = bytearray(data)
resp = self._send_receive_raw(data)
errorcode = resp[12]
if not (errorcode == BlobReturnCodes.SUCCESS or errorcode == BlobReturnCodes.NOTMODIFIED):
raise HpIloError(errorcode)
self.unloadchifhandle(lib)
return resp
def gaius_media_mount(self):
"""Operation to mount the gaius media partition"""
lib = self.gethprestchifhandle()
lib.gaius_media_mount.argtypes = []
lib.gaius_media_mount.restype = POINTER(c_ubyte)
ptr = lib.gaius_media_mount()
data = ptr[: lib.size_of_embeddedMediaRequest()]
data = bytearray(data)
resp = self._send_receive_raw(data)
errorcode = resp[12]
if not (errorcode == BlobReturnCodes.SUCCESS or errorcode == BlobReturnCodes.NOTMODIFIED):
raise HpIloError(errorcode)
self.unloadchifhandle(lib)
return resp
def vid_media_mount(self):
"""Operation to mount the gaius media partition"""
lib = self.gethprestchifhandle()
lib.vid_media_mount.argtypes = []
lib.vid_media_mount.restype = POINTER(c_ubyte)
ptr = lib.vid_media_mount()
data = ptr[: lib.size_of_embeddedMediaRequest()]
data = bytearray(data)
resp = self._send_receive_raw(data)
errorcode = resp[12]
if not (errorcode == BlobReturnCodes.SUCCESS or errorcode == BlobReturnCodes.NOTMODIFIED):
raise HpIloError(errorcode)
self.unloadchifhandle(lib)
return resp
def mountflat(self):
"""Operation to mount the gaius media partition"""
lib = self.gethprestchifhandle()
lib.flat_media_mount.argtypes = []
lib.flat_media_mount.restype = POINTER(c_ubyte)
ptr = lib.flat_media_mount()
data = ptr[: lib.size_of_embeddedMediaRequest()]
data = bytearray(data)
resp = self._send_receive_raw(data)
errorcode = resp[12]
if not (errorcode == BlobReturnCodes.SUCCESS or errorcode == BlobReturnCodes.NOTMODIFIED):
raise HpIloError(errorcode)
self.unloadchifhandle(lib)
return resp
def media_unmount(self):
"""Operation to unmount the media partition"""
lib = self.gethprestchifhandle()
lib.media_unmount.argtypes = []
lib.media_unmount.restype = POINTER(c_ubyte)
ptr = lib.media_unmount()
data = ptr[: lib.size_of_embeddedMediaRequest()]
data = bytearray(data)
resp = self._send_receive_raw(data)
errorcode = resp[12]
if not (errorcode == BlobReturnCodes.SUCCESS or errorcode == BlobReturnCodes.NOTMODIFIED):
raise HpIloError(errorcode)
self.unloadchifhandle(lib)
return resp
def bb_media_unmount(self):
"""Operation to unmount the media partition"""
lib = self.gethprestchifhandle()
lib.bb_media_unmount.argtypes = []
lib.bb_media_unmount.restype = POINTER(c_ubyte)
ptr = lib.bb_media_unmount()
data = ptr[: lib.size_of_embeddedMediaRequest()]
data = bytearray(data)
resp = self._send_receive_raw(data)
errorcode = resp[12]
if not (errorcode == BlobReturnCodes.SUCCESS or errorcode == BlobReturnCodes.NOTMODIFIED):
raise HpIloError(errorcode)
self.unloadchifhandle(lib)
return resp
def vid_media_unmount(self):
"""Operation to unmount the media partition"""
lib = self.gethprestchifhandle()
lib.vid_media_unmount.argtypes = []
lib.vid_media_unmount.restype = POINTER(c_ubyte)
ptr = lib.vid_media_unmount()
data = ptr[: lib.size_of_embeddedMediaRequest()]
data = bytearray(data)
resp = self._send_receive_raw(data)
errorcode = resp[12]
if not (errorcode == BlobReturnCodes.SUCCESS or errorcode == BlobReturnCodes.NOTMODIFIED):
raise HpIloError(errorcode)
self.unloadchifhandle(lib)
return resp
def gaius_media_unmount(self):
"""Operation to unmount the media partition"""
lib = self.gethprestchifhandle()
lib.gaius_media_unmount.argtypes = []
lib.gaius_media_unmount.restype = POINTER(c_ubyte)
ptr = lib.gaius_media_unmount()
data = ptr[: lib.size_of_embeddedMediaRequest()]
data = bytearray(data)
resp = self._send_receive_raw(data)
errorcode = resp[12]
if not (errorcode == BlobReturnCodes.SUCCESS or errorcode == BlobReturnCodes.NOTMODIFIED):
raise HpIloError(errorcode)
self.unloadchifhandle(lib)
return resp
def absr_media_unmount(self):
"""Operation to unmount the media partition"""
lib = self.gethprestchifhandle()
lib.absaroka_media_unmount.argtypes = []
lib.absaroka_media_unmount.restype = POINTER(c_ubyte)
ptr = lib.absaroka_media_unmount()
data = ptr[: lib.size_of_embeddedMediaRequest()]
data = bytearray(data)
resp = self._send_receive_raw(data)
errorcode = resp[12]
if not (errorcode == BlobReturnCodes.SUCCESS or errorcode == BlobReturnCodes.NOTMODIFIED):
raise HpIloError(errorcode)
self.unloadchifhandle(lib)
return resp
def _send_receive_raw(self, indata):
"""Send and receive raw function for blob operations
:param indata: The data to be sent to blob operation.
:type indata: str.
"""
LOGGER.info("Starting _send_receive_raw operation.")
excp = None
for attempt in range(1, 4): # 3 attempts
try:
LOGGER.debug(f"Attempt {attempt}: Sending data to iLO. Data size: {len(indata)} bytes")
resp = self.channel.send_receive_raw(indata, 10)
# Log response details
errorcode = None
if len(resp) >= 12:
errorcode = struct.unpack("<I", bytes(resp[8:12]))[0]
LOGGER.debug(
f"Response details - Size: {len(resp)} bytes, Error Code: {errorcode}, "
f"Headers: {resp[:8].hex()}"
)
LOGGER.info("Data successfully sent and received on attempt %d.", attempt)
return resp
except Exception as exp:
LOGGER.warning("Attempt %d failed. Error: %s", attempt, str(exp))
self.channel.close()
LOGGER.info("Reinitializing communication channel with iLO.")
lib = self.gethprestchifhandle()
self.channel = HpIlo(dll=lib)
excp = exp # Store last exception for final raise
LOGGER.error("All attempts to send/receive raw data have failed.")
if excp:
raise excp # Raise only after all retries fail
def cert_login(self, cert_file, priv_key, key_pass):
lib = self.gethprestchifhandle()
lib.login_cert.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p]
lib.login_cert.restype = c_char_p
token = lib.login_cert(self.channel.fhandle, cert_file, priv_key, key_pass)
return token
@staticmethod
def gethprestchifhandle():
"""Multi-platform handle for Chif hprest library"""
if platform.system() == "Darwin":
raise ChifDllMissingError()
LOGGER.debug("Retrieving Chif library handle.")
excp = None
libhandle = None
arch = platform.machine().lower()
is_windows = os.name == "nt"
def load_with_flags(path):
"""Load library using RTLD_LAZY | RTLD_GLOBAL to match dlopen behavior."""
return ctypes.CDLL(path, mode=RTLD_LAZY | RTLD_GLOBAL)
# 1. Try fixed path on Linux
if not is_windows:
libpath = "/opt/ilorest/lib64/libilorestchif.so"
if os.path.isfile(libpath):
try:
libhandle = load_with_flags(libpath)
except Exception as exp:
excp = exp
# 2. Try current directory for known filenames
if not libhandle:
libnames = ["ilorest_chif.dll"] if is_windows else ["ilorest_chif_dev.so", "ilorest_chif.so"]
for libname in libnames:
try:
libpath = BlobStore2.checkincurrdirectory(libname)
libhandle = load_with_flags(libpath)
if libhandle:
break
except Exception as exp:
excp = exp
# 3. Try locating from installed site-packages
if not libhandle:
try:
site_packages = [sysconfig.get_paths()["purelib"]]
except Exception as exp:
LOGGER.debug("Failed to get site-packages path: %s", str(exp))
site_packages = []
for package in site_packages:
try:
# Base chif library path
chif_dir = os.path.join(package, "ilorest", "chiflibrary")
if is_windows:
chif_lib_name = "ilorest_chif.dll"
libpath = os.path.join(chif_dir, chif_lib_name)
else:
chif_lib_name = "ilorest_chif.so"
libpath = os.path.join(chif_dir, chif_lib_name)
# Try arch-specific subfolder as fallback
if arch in ("aarch64", "arm64"):
libpath = os.path.join(chif_dir, "arm", chif_lib_name)
if os.path.isfile(libpath):
libhandle = load_with_flags(libpath)
if libhandle:
break
except Exception as exp:
excp = exp
# Final result
if libhandle:
LOGGER.debug("Successfully loaded Chif library.")
BlobStore2.setglobalhprestchifrandnumber(libhandle)
return libhandle
else:
LOGGER.debug("Failed to load Chif library: %s", str(excp))
raise ChifDllMissingError(excp)
@staticmethod
def setglobalhprestchifrandnumber(libbhndl):
"""Set the random number for the chif handle
:param libbhndl: The library handle provided by loading the chif library.
:type libbhndl: library handle.
"""
rndval = random.randint(1, 65535)
libbhndl.updaterandval.argtypes = [c_ushort]
libbhndl.updaterandval(rndval)
@staticmethod
def initializecreds(username=None, password=None, log_dir=None):
"""
Initialize Chif and handle high-security credentials.
:param username: The username to login.
:type username: str
:param password: The password to login.
:type password: str
:param log_dir: The directory where logs will be stored.
:type log_dir: str
:return: True if successful, False otherwise.
:rtype: bool
:raises Blob2SecurityError: If security check fails.
:raises HpIloInitialError: If credential verification fails.
"""
LOGGER.info("Initializing Chif credentials with security settings.")
LOGGER.debug("Username provided: %s, Log directory: %s", username, log_dir)
try:
dll = BlobStore2.gethprestchifhandle()
# Enable debug output if LOGGER level is DEBUG
if LOGGER.isEnabledFor(logging.DEBUG) and log_dir:
logdir_c = create_string_buffer(log_dir.encode("utf-8"))
LOGGER.debug("Enabling debug output to directory: %s", log_dir)
dll.enabledebugoutput(logdir_c)
# Initialize Chif
LOGGER.debug("Calling ChifInitialize()")
dll.ChifInitialize(None)
# If username is provided, proceed with security authentication
if username:
if not password:
LOGGER.warning("Password is missing while username is provided.")
return False # Invalid credentials
# Check security level requirement
LOGGER.debug("Checking iLO security requirements")
security_level = dll.ChifIsSecurityRequired()
LOGGER.debug(f"Security level check returned: {security_level}")
if security_level > 0:
LOGGER.info("High security mode detected. Authenticating credentials.")
LOGGER.debug(f"Security requirements: Username={username}, High Security Mode=True")
dll.initiate_credentials.argtypes = [c_char_p, c_char_p]
dll.initiate_credentials.restype = POINTER(c_ubyte)
usernew = create_string_buffer(username.encode("utf-8"))
passnew = create_string_buffer(password.encode("utf-8"))
LOGGER.debug("Initiating credential verification.")
dll.initiate_credentials(usernew, passnew)
credreturn = dll.ChifVerifyCredentials()
if credreturn == BlobReturnCodes.SUCCESS:
LOGGER.info("Credentials verified successfully.")
elif credreturn == hpiloreturncodes.CHIFERR_AccessDenied:
LOGGER.error("Access Denied: Invalid credentials.")
raise Blob2SecurityError()
else:
LOGGER.error("Error %s occurred while trying to open a channel to iLO.", credreturn)
raise HpIloInitialError(f"Error {credreturn} occurred while trying to open a channel to iLO.")
else:
LOGGER.info("Security not required. Disabling security.")
dll.ChifDisableSecurity()
else:
LOGGER.debug("No username provided. Checking security requirement.")
if dll.ChifIsSecurityRequired() > 0:
LOGGER.warning("High security mode detected but no credentials provided.")
return False
else:
LOGGER.info("Security not required. Disabling security.")
dll.ChifDisableSecurity()
return True
except Exception as e:
LOGGER.exception("Exception occurred during Chif initialization: %s", str(e))
raise
finally:
LOGGER.debug("Unloading Chif handle.")
BlobStore2.unloadchifhandle(dll)
@staticmethod
def checkincurrdirectory(libname):
"""Check if the library is present in current directory.
:param libname: The name of the library to search for.
:type libname: str."""
libpath = libname
if os.path.isfile(os.path.join(os.path.split(sys.executable)[0], libpath)):
libpath = os.path.join(os.path.split(sys.executable)[0], libpath)
elif os.path.isfile(os.path.join(os.getcwd(), libpath)):
libpath = os.path.join(os.getcwd(), libpath)
elif os.environ.get("LD_LIBRARY_PATH"):
paths = os.getenv("LD_LIBRARY_PATH", libpath).split(";")
libpath = [os.path.join(pat, libname) for pat in paths if os.path.isfile(os.path.join(pat, libname))]
libpath = libpath[0] if libpath else libname
return libpath
@staticmethod
def unloadchifhandle(lib):
"""
Release a handle on the Chif iLOrest library.
:param lib: The library handle provided by loading the Chif library.
:type lib: library handle
"""
if lib is None:
LOGGER.warning("unloadchifhandle called with None library handle. Nothing to release.")
return
try:
libhandle = lib._handle
LOGGER.info("Releasing Chif library handle: %s", libhandle)
if os.name == "nt":
LOGGER.debug("Using Windows FreeLibrary to release handle.")
windll.kernel32.FreeLibrary(None, handle=libhandle)
else:
LOGGER.debug("Using dlclose to release handle on Linux/Unix.")
dlclose(libhandle)
LOGGER.info("Successfully released Chif library handle.")
except Exception as e:
LOGGER.exception("Error while unloading Chif library handle: %s", str(e))