blob: 3089d86df5eda7941f38894404460aa59d33141f [file] [edit]
import json
import sys
import pandas as pd
#########################################################################
# Excel config and mapping of row and col
# Define constants for link details parsing
LINK_DETAILS_COLUMNS = "A:F"
LINK_DETAILS_HEADER_ROW = 1
# Define constants for device details parsing
DEVICE_DETAILS_COLUMNS = "H:J"
DEVICE_DETAILS_HEADER_ROW = 1
# Define constants for prefix details parsing
PREFIX_DETAILS_COLUMNS = "L:N"
PREFIX_DETAILS_HEADER_ROW = 1
#########################################################################
# Store device details in dictionary
device_inventory_paths = {}
device_probe = {}
# Store link connection details in dictionary
topology_data = {}
# Keeping track of logical port ID [1 based] and physical port ID
device_physical_to_logical_port = {}
# Generating final EM json config
final_config = []
# Store prefix details in a dictionary
prefix_data = {
"GPU": {
"GPU": "None",
"SWITCH": "None",
"PCIE_BRIDGE": "None",
"CPU": "None",
"EXTERNAL": "None",
},
"SWITCH": {
"GPU": "None",
"SWITCH": "None",
"PCIE_BRIDGE": "None",
"CPU": "None",
"EXTERNAL": "None",
},
"PCIE_BRIDGE": {
"GPU": "None",
"SWITCH": "None",
"PCIE_BRIDGE": "None",
"CPU": "None",
"EXTERNAL": "None",
},
"CPU": {
"GPU": "None",
"SWITCH": "None",
"PCIE_BRIDGE": "None",
"CPU": "None",
"EXTERNAL": "None",
},
"EXTERNAL": {
"GPU": "None",
"SWITCH": "None",
"PCIE_BRIDGE": "None",
"CPU": "None",
"EXTERNAL": "None",
},
}
#########################################################################
def getDataFromExcel(input_file):
device_details_df = pd.read_excel(
input_file,
engine="openpyxl",
usecols=DEVICE_DETAILS_COLUMNS,
skiprows=DEVICE_DETAILS_HEADER_ROW - 1,
)
prefix_details_df = pd.read_excel(
input_file,
engine="openpyxl",
usecols=PREFIX_DETAILS_COLUMNS,
skiprows=PREFIX_DETAILS_HEADER_ROW - 1,
)
link_details_df = pd.read_excel(
input_file,
engine="openpyxl",
usecols=LINK_DETAILS_COLUMNS,
skiprows=LINK_DETAILS_HEADER_ROW - 1,
)
# Get device details
for index, row in device_details_df.iterrows():
device_type = str(row["DeviceType"]).upper()
if "NAN" not in (device_type):
device_inventory_paths[device_type] = str(row["DbusInventoryPath"])
device_probe[device_type] = str(row["Probe"]).upper()
# Get prefix details
for index, row in prefix_details_df.iterrows():
device_1 = str(row["Device1"]).upper()
device_2 = str(row["Device2"]).upper()
if "NAN" not in (device_1, device_2):
prefix_data[device_1][device_2] = str(row["LinkPrefix"])
# Get link details
for index, row in link_details_df.iterrows():
left_device_id = (
int(row["LeftDeviceInstanceID"])
if row["LeftDeviceInstanceID"] != "-"
else None
)
left_device_type = (
str(row["LeftDeviceType"]).upper()
if row["LeftDeviceType"] != "-"
else "EXTERNAL"
)
left_port_int = (
int(row["LeftDevicePortID"])
if row["LeftDevicePortID"] != "-"
else None
)
right_device_id = (
int(row["RightDeviceInstanceID"])
if row["RightDeviceInstanceID"] != "-"
else None
)
right_device_type = (
str(row["RightDeviceType"]).upper()
if row["RightDeviceType"] != "-"
else "EXTERNAL"
)
right_port_int = (
int(row["RightDevicePortID"])
if row["RightDevicePortID"] != "-"
else None
)
if (
left_device_type != "EXTERNAL"
and left_device_type not in topology_data
):
topology_data[left_device_type] = {}
if (
right_device_type != "EXTERNAL"
and right_device_type not in topology_data
):
topology_data[right_device_type] = {}
# Generate link data for the left side device of the link
if left_port_int is not None:
link_data_for_left_device = {
"connected_device_id": right_device_id,
"connected_device_type": right_device_type,
"connected_device_port_id": right_port_int,
"self_port_id": left_port_int,
}
topology_data[left_device_type].setdefault(
left_device_id, []
).append(link_data_for_left_device)
# Generate link data for the right side device of the link
if right_port_int is not None:
link_data_for_right_device = {
"connected_device_id": left_device_id,
"connected_device_type": left_device_type,
"connected_device_port_id": left_port_int,
"self_port_id": right_port_int,
}
topology_data[right_device_type].setdefault(
right_device_id, []
).append(link_data_for_right_device)
# For debug purpose only---------------------------------------------
# json_data = json.dumps(topology_data, indent=4)
# with open('topology_data.json', 'w', encoding='utf-8') as json_file:
# json_file.write(json_data)
# json_data = json.dumps(prefix_data, indent=4)
# with open('prefix_data.json', 'w', encoding='utf-8') as json_file:
# json_file.write(json_data)
# For debug purpose only---------------------------------------------
#########################################################################
def sortTopologyData():
# Sort the link data based on the physical port Id [self_port_id]
for device_type, all_device_data in topology_data.items():
for device_id in all_device_data.keys():
all_device_data[device_id] = sorted(
all_device_data[device_id], key=lambda x: x["self_port_id"]
)
# For debug purpose only---------------------------------------------
# json_data = json.dumps(topology_data, indent=4)
# with open('sorted_topology_data.json', 'w', encoding='utf-8') as json_file:
# json_file.write(json_data)
# For debug purpose only---------------------------------------------
#########################################################################
def getPhysicalToLogicalMapping():
for self_device_type, all_device_data in topology_data.items():
port_mapping = {}
for self_device_id in all_device_data.keys():
device_map = {}
port_count_tracker = {
"GPU": 0,
"SWITCH": 0,
"PCIE_BRIDGE": 0,
"CPU": 0,
"EXTERNAL": 0,
}
logical_id = 1
for link_data in all_device_data[self_device_id]:
# Get the port ID and assign the logical_id to it
self_port_id = link_data["self_port_id"]
connected_device_type = link_data["connected_device_type"]
device_map[self_port_id] = (
logical_id,
f"{prefix_data[self_device_type][connected_device_type]}_{port_count_tracker[connected_device_type]}",
)
port_count_tracker[connected_device_type] += 1
logical_id += 1
port_mapping[self_device_id] = device_map
device_physical_to_logical_port[self_device_type] = port_mapping
# For debug purpose only---------------------------------------------
# json_data_next = json.dumps(device_physical_to_logical_port, indent=4)
# with open('device_physical_to_logical_port.json', 'w', encoding='utf-8') as json_file:
# json_file.write(json_data_next)
# For debug purpose only---------------------------------------------
#########################################################################
def getAssociations(
self_device_type,
connected_device_type,
connected_device_path,
connected_port_path,
):
association = []
if self_device_type == "GPU":
if connected_device_type == "SWITCH":
association.extend(
("associated_switch", "connected_port", connected_device_path)
)
association.extend(
("switch_port", "processor_port", connected_port_path)
)
elif self_device_type == "SWITCH":
if connected_device_type == "GPU":
gpu_device_name = connected_device_path.split("/")[-1]
connected_gpu_endpoint = f"/xyz/openbmc_project/inventory/system/fabrics/HGX_NVLinkFabric_0/Endpoints/{gpu_device_name}"
association.extend(
(
"associated_endpoint",
"connected_port",
connected_gpu_endpoint,
)
)
association.extend(
("processor_port", "switch_port", connected_port_path)
)
elif connected_device_type == "SWITCH":
association.extend(
("associated_switch", "connected_port", connected_device_path)
)
association.extend(
("switch_port", "switch_port", connected_port_path)
)
elif connected_device_type == "PCIE_BRIDGE":
association.extend(
(
"associated_pcie_bridge",
"connected_port",
connected_device_path,
)
)
association.extend(
("pcie_bridge_port", "switch_port", connected_port_path)
)
elif self_device_type == "PCIE_BRIDGE":
if connected_device_type == "SWITCH":
association.extend(
("associated_switch", "connected_port", connected_device_path)
)
association.extend(
("switch_port", "pcie_bridge_port", connected_port_path)
)
return association
#########################################################################
def generateConfig():
for self_device_type, all_device_data in topology_data.items():
device_expose = []
for self_device_id, self_link_data in all_device_data.items():
device_all_ports = []
port_count = 0
for link_data in self_link_data:
self_port_id = link_data["self_port_id"]
connected_device_id = link_data["connected_device_id"]
connected_device_type = link_data["connected_device_type"]
connected_port_id = link_data["connected_device_port_id"]
association = []
self_logical_id, self_port_name = (
device_physical_to_logical_port[self_device_type][
self_device_id
][self_port_id]
)
self_device_path = device_inventory_paths[
self_device_type
].replace("$INSTANCE_NUMBER", str(self_device_id))
self_port_path = f"{self_device_path}/Ports/{self_port_name}"
if connected_device_type != "EXTERNAL":
connected_logical_id, connected_port_name = (
device_physical_to_logical_port[connected_device_type][
connected_device_id
][connected_port_id]
)
connected_device_path = device_inventory_paths[
connected_device_type
].replace("$INSTANCE_NUMBER", str(connected_device_id))
connected_port_path = (
f"{connected_device_path}/Ports/{connected_port_name}"
)
association = getAssociations(
self_device_type,
connected_device_type,
connected_device_path,
connected_port_path,
)
else:
association.extend(("", "", ""))
device_all_ports.append(
{
"InventoryObjPath": self_port_path,
"LogicalPortNumber": self_logical_id,
"Associations": association,
}
)
port_count += 1
device_name = self_device_path.split("/")[-1]
device_expose.append(
{
"Name": device_name,
"Type": "NVLinkTopology",
"Count": port_count,
"Topology": device_all_ports,
}
)
# Add the device expose list to the dictionary by device type
final_config.append(
{
"Exposes": device_expose,
"Probe": device_probe[self_device_type],
"Name": self_device_type,
"Type": "LinkTopology",
}
)
#########################################################################
def main(input_file, output_file):
# Step:1 Read excel file and get all the data into structure
getDataFromExcel(input_file)
# Step:2 Sort the link data based on the physical port Id [self_port_id]
sortTopologyData()
# Step:3 Create mapping between logical and physical port Id
getPhysicalToLogicalMapping()
# Step:4 Generate final configuration
generateConfig()
# Convert generated config to JSON format
json_data_next = json.dumps(final_config, indent=4)
with open(output_file, "w", encoding="utf-8") as json_file:
json_file.write(json_data_next)
print("##### JSON data written to " + output_file + " #####")
#########################################################################
if __name__ == "__main__":
if len(sys.argv) != 3:
print(
"Usage: python nvlink_topology_processor.py <input_mapping_file> <output_json_file>"
)
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2]
main(input_file, output_file)