diff --git a/src/infragraph/blueprints/devices/cisco/ucs_c88_5A_m8.py b/src/infragraph/blueprints/devices/cisco/ucs_c88_5A_m8.py new file mode 100644 index 0000000..7501cee --- /dev/null +++ b/src/infragraph/blueprints/devices/cisco/ucs_c88_5A_m8.py @@ -0,0 +1,198 @@ +from infragraph.infragraph import * +from typing import Optional, Union + +XpuType=Literal["hgx_h100","hgx_h200","mi300x","mi350x"] + +class cisco_ucsc_885A_m8(Device): + def __init__(self, xpu_type: XpuType= "hgx_h100", storage_count: int=16, ns_nic_count: int=2, ew_nic_count: int=2): + super(Device, self).__init__() + self.name = "ucsc_885A_m8" + self.description = "Cisco UCS C885A M8 Rack Server" + + XPU_CATALOG = { + "hgx_h100":{ + "description": "NVIDIA HGX H100 SXM", + "fabric": "nvlink_4", + "count": 8, + "nvswitch_count": 4, + }, + "hgx_h200": { + "description": "NVIDIA HGX H200 SXM", + "fabric": "nvlink_4", + "count": 8, + "nvswitch_count": 4, + }, + "mi300x": { + "description": "AMD MI300X OAM", + "fabric": "Infinity_fabric", + "count": 8, + "nvswitch_count": 0, + }, + "mi350x":{ + "description": "AMD MI350X OAM", + "fabric": "Infinity_fabric", + "count": 8, + "nvswitch_count": 0, + } + } + + xpu_cfg= XPU_CATALOG[xpu_type] + + cpu = self.components.add( + name="cpu", + description="AMD EPYC 9554/9575F/9535 64 core", + count=2, + ) + cpu.choice = Component.CPU + + memory = self.components.add( + name="memory", + description="DDR5 RDIMM (24 slots, 64/96/128 GB, up to 6000 MT/s)", + count=24, + ) + memory.choice = Component.MEMORY + + xpu= self.components.add( + name="xpu", + description=xpu_cfg["description"], + count=xpu_cfg["count"], + ) + xpu.choice=Component.XPU + + nvswitch_count = xpu_cfg["nvswitch_count"] + nvsw = None + if nvswitch_count > 0: + nvsw = self.components.add( + name="nvsw", + description="NVIDIA NVSwitch", + count=nvswitch_count, + ) + nvsw.choice = Component.SWITCH + + storage = self.components.add( + name="storage", + description="2.5” U.2 NVMe SSD", + count=storage_count, + ) + storage.choice = Component.CUSTOM + + ew_nic =self.components.add( + name="east_west_nic", + description="East-West NIC (ConnectX-7 1x400G, BlueField-3 B3140H SuperNIC, or AMD Pensando Pollara 400)", + count=ew_nic_count, + ) + ew_nic.choice = Component.NIC + + ns_nic =self.components.add( + name="north_south_nic", + description="North-South NIC (ConnectX-7 2x200G or BlueField-3 B3220/B3240)", + count=ns_nic_count, + ) + ns_nic.choice = Component.NIC + + ocp = self.components.add( + name="ocp", + description="OCP 3.0 Intel X710-T2L 2x10G RJ45 NIC", + count=1, + ) + ocp.choice = Component.NIC + + ns_pciesl = self.components.add( + name="ns_pciesl", + description="PCIe Gen5 x16 FHHL slots for North-South NIC", + count=5, + ) + ns_pciesl.choice = Component.CUSTOM + ns_pciesl.custom.type = "pcie_slot" + + ew_pciesl = self.components.add( + name="ew_pciesl", + description="PCIe Gen5 x16 HHHL slots for East-West NIC (1 per GPU)", + count=8, + ) + ew_pciesl.choice = Component.CUSTOM + ew_pciesl.custom.type = "pcie_slot" + + #links + xgmi = self.links.add(name="infinity_fabric", description="AMD xGMI CPU fabric") + xpu_fabric_name = xpu_cfg["fabric"] + xpu_link = self.links.add(name=xpu_fabric_name, description=f"{xpu_fabric_name} XPU fabric") + pci = self.links.add(name="pcie_gen5", description="PCI Express Gen 5.0 x16") + ddr5 = self.links.add(name="ddr5", description="DDR5 up to 6000 MT/s") + + #CPU + edge = self.edges.add(DeviceEdge.MANY2MANY, link=xgmi.name) + edge.ep1.component = f"{cpu.name}" + edge.ep2.component = f"{cpu.name}" + + #CPU to GPUs + gpus_per_cpu = xpu.count // cpu.count + for cpu_index in range(cpu.count): + e = self.edges.add(DeviceEdge.MANY2MANY, link=pci.name) + e.ep1.component = f"{cpu.name}[{cpu_index}]" + e.ep2.component = f"{xpu.name}[{cpu_index * gpus_per_cpu}:{cpu_index * gpus_per_cpu + gpus_per_cpu}]" + + #CPU to NS PCIe slots + cpu_to_ns_pciesl = {0: [0, 1, 2], + 1: [3, 4]} + for cpu_index, slots in cpu_to_ns_pciesl.items(): + e = self.edges.add(DeviceEdge.MANY2MANY, link=pci.name) + e.ep1.component = f"{cpu.name}[{cpu_index}]" + e.ep2.component = f"{ns_pciesl.name}[{slots[0]}:{slots[-1] + 1}]" + + #CPU to EW PCIe slots + ew_per_cpu = ew_pciesl.count // cpu.count + for cpu_index in range(cpu.count): + e = self.edges.add(DeviceEdge.MANY2MANY, link=pci.name) + e.ep1.component = f"{cpu.name}[{cpu_index}]" + e.ep2.component = f"{ew_pciesl.name}[{cpu_index * ew_per_cpu}:{cpu_index * ew_per_cpu + ew_per_cpu}]" + + #Storage: + drives_per_cpu = storage.count // cpu.count + for cpu_index in range(cpu.count): + e = self.edges.add(DeviceEdge.MANY2MANY, link=pci.name) + e.ep1.component = f"{cpu.name}[{cpu_index}]" + e.ep2.component = f"{storage.name}[{cpu_index * drives_per_cpu}:{cpu_index * drives_per_cpu + drives_per_cpu}]" + + #XPU fabric + if xpu_fabric_name == "nvlink_4": + e = self.edges.add(DeviceEdge.MANY2MANY, link=xpu_link.name) + e.ep1.component = f"{xpu.name}[0:{xpu.count}]" + e.ep2.component = f"{nvsw.name}[0:{nvswitch_count}]" + elif xpu_fabric_name == "infinity_fabric": + e = self.edges.add(DeviceEdge.MANY2MANY, link=xpu_link.name) + e.ep1.component = f"{xpu.name}[0:{xpu.count}]" + e.ep2.component = f"{xpu.name}[0:{xpu.count}]" + + #Memory + dimms_per_cpu = memory.count // cpu.count + for cpu_index in range(cpu.count): + start = cpu_index * dimms_per_cpu + e = self.edges.add(DeviceEdge.MANY2MANY, link=ddr5.name) + e.ep1.component = f"{cpu.name}[{cpu_index}]" + e.ep2.component = f"{memory.name}[{start}:{start + dimms_per_cpu}]" + + #NIC - East-West NICs plug into EW slots (1:1) + if ew_nic is not None: + for nic_index in range(ew_nic.count): + e = self.edges.add(DeviceEdge.ONE2ONE, link=pci.name) + e.ep1.component = f"{ew_pciesl.name}[{nic_index}]" + e.ep2.component = f"{ew_nic.name}[{nic_index}]" + + #NIC - North-South NICs plug into NS slots (1:1 starting at slot 0) + for nic_index in range(ns_nic.count): + e = self.edges.add(DeviceEdge.ONE2ONE, link=pci.name) + e.ep1.component = f"{ns_pciesl.name}[{nic_index}]" + e.ep2.component = f"{ns_nic.name}[{nic_index}]" + + #NIC - OCP + e = self.edges.add(DeviceEdge.ONE2ONE, link=pci.name) + e.ep1.component = f"{cpu.name}[0]" + e.ep2.component = f"{ocp.name}[0]" + + + + +if __name__ == "__main__": + device = cisco_ucsc_885A_m8() + print(device.serialize(encoding=Device.YAML)) \ No newline at end of file diff --git a/src/infragraph/blueprints/devices/dell/z9864f_on.py b/src/infragraph/blueprints/devices/dell/z9864f_on.py new file mode 100644 index 0000000..e437373 --- /dev/null +++ b/src/infragraph/blueprints/devices/dell/z9864f_on.py @@ -0,0 +1,74 @@ +from infragraph.infragraph import * + +class DellPowerSwitchZ9864FOn(Device): + """ + InfraGraph blueprint of the Dell PowerSwitch Z9864F-ON. + """ + + OSFP_PORT_COUNT = 64 + SFP_PORT_COUNT = 2 + + def __init__(self): + """Adds an InfraGraph device to infrastructure based on the following components: + - 1 CPU + - 64 osfp112 ports (800GbE, multi-rate capable) + - 2 SFP+ ports (10GbE) + - integrated circuitry connecting all ports to the ASIC + """ + super(Device, self).__init__() + self.name = "powerswitch_z9864f_on" + self.description = "Dell PowerSwitch Z9864F-ON 64x400GbE aggregation switch" + + cpu = self.components.add( + name="cpu", + description=" Intel Xeon D-1714", + count=1, + ) + cpu.choice = Component.CPU + + memory=self.components.add( + name="memory", + description="32GB DDR4 ECC", + count=1, + ) + + osfp_ports = self.components.add( + name="osfp_port", + description="OSFP112 800GbE multi-rate port", + count=self.OSFP_PORT_COUNT, + ) + osfp_ports.choice = Component.PORT + + sfp_ports = self.components.add( + name="sfp_port", + description="SFP+ 10GbE port", + count=self.SFP_PORT_COUNT, + ) + sfp_ports.choice = Component.PORT + + ic = self.links.add( + name="ic", + description="Integrated circuitry between ASIC and ports", + ) + + mem_bus=self.links.add( + name="mem_bus", + description="ddr4 memory bus", + ) + + qsfp_edge = self.edges.add(scheme=DeviceEdge.MANY2MANY, link=ic.name) + qsfp_edge.ep1.component = cpu.name + qsfp_edge.ep2.component = osfp_ports.name + + sfp_edge = self.edges.add(scheme=DeviceEdge.MANY2MANY, link=ic.name) + sfp_edge.ep1.component = cpu.name + sfp_edge.ep2.component = sfp_ports.name + + mem_edge=self.edges.add(scheme=DeviceEdge.ONE2ONE, link=mem_bus.name) + mem_edge.ep1.component=cpu.name + mem_edge.ep2.component=memory.name + + +if __name__ == "__main__": + device = DellPowerSwitchZ9864FOn() + print(device.serialize(encoding=Device.YAML)) \ No newline at end of file diff --git a/src/tests/test_blueprints/test_cisco.py b/src/tests/test_blueprints/test_cisco.py new file mode 100644 index 0000000..02069e5 --- /dev/null +++ b/src/tests/test_blueprints/test_cisco.py @@ -0,0 +1,39 @@ +import pytest +import networkx +from infragraph.infragraph import Api +from infragraph.infragraph_service import InfraGraphService +from infragraph.blueprints.devices.cisco.ucs_c88_5A_m8 import cisco_ucsc_885A_m8 + + +@pytest.mark.asyncio +@pytest.mark.parametrize("count", [1, 2]) +@pytest.mark.parametrize( + "device", + [ + cisco_ucsc_885A_m8(xpu_type="hgx_h100"), + cisco_ucsc_885A_m8(xpu_type="hgx_h200"), + cisco_ucsc_885A_m8(xpu_type="mi300x"), + cisco_ucsc_885A_m8(xpu_type="mi350x"), + ], +) +async def test_ucsc_885A_m8(count, device): + """From a Cisco UCS C885A M8 device, generate a graph and validate the graph. + + - with a count > 1 there should be no connectivity between device instances + """ + # create the graph + device.validate() + infrastructure = Api().infrastructure() + infrastructure.devices.append(device) + infrastructure.instances.add(name=device.name, device=device.name, count=count) + service = InfraGraphService() + service.set_graph(infrastructure) + + # validations + g = service.get_networkx_graph() + print(f"\ndevice {device.name} is a {g}") + print(networkx.write_network_text(g, vertical_chains=True)) + + +if __name__ == "__main__": + pytest.main(["-s", __file__]) \ No newline at end of file diff --git a/src/tests/test_blueprints/test_z9864f_on.py b/src/tests/test_blueprints/test_z9864f_on.py new file mode 100644 index 0000000..aef2bc8 --- /dev/null +++ b/src/tests/test_blueprints/test_z9864f_on.py @@ -0,0 +1,43 @@ +import networkx +import pytest +from infragraph.infragraph import Api +from infragraph.infragraph_service import InfraGraphService +from infragraph.blueprints.devices.dell.z9864f_on import DellPowerSwitchZ9864FOn +from infragraph.visualizer.visualize import run_visualizer + +def print_graph(graph): + for node, attrs in graph.nodes(data=True): + print(f"Node: {node}, Attributes: {attrs}") + + for u, v, attrs in graph.edges(data=True): + print(f"Edge: ({u}, {v}), Attributes: {attrs}") + pass + +@pytest.mark.asyncio +@pytest.mark.parametrize("count", [1, 2]) +async def test_z9864fon(count): + # create device + device = DellPowerSwitchZ9864FOn() + + # create infrastructure + infrastructure = Api().infrastructure() + infrastructure.devices.append(device) + infrastructure.instances.add( + name=device.name, + device=device.name, + count=count, + ) + + # validate + device.validate() + service = InfraGraphService() + service.set_graph(infrastructure) + + # validations + g = service.get_networkx_graph() + print(f"\ndevice {device.name} is a {g}") + print(networkx.write_network_text(g, vertical_chains=True)) + print_graph(g) + +if __name__ == "__main__": + pytest.main(["-s", __file__]) \ No newline at end of file