From c6fa18a95c1d87a72a7a8635cee458858713382e Mon Sep 17 00:00:00 2001 From: Harsh Sikhwal Date: Tue, 5 May 2026 12:49:23 +0530 Subject: [PATCH 01/24] annotation new schema and service changes --- api/api.yaml | 2 +- artifacts/infragraph.proto | 132 ++++++++-------- artifacts/openapi.yaml | 195 +++++++++++++----------- models/annotation.yaml | 126 ++++++++------- models/graph.yaml | 77 ---------- src/infragraph/infragraph_service.py | 133 +++++++++++++++- src/tests/test_expand_node_string.py | 76 +++++++++ src/tests/test_ipaddress_annotations.py | 15 +- src/tests/test_rank_annotations.py | 14 +- src/tests/test_shortest_path.py | 9 +- 10 files changed, 475 insertions(+), 304 deletions(-) create mode 100644 src/tests/test_expand_node_string.py diff --git a/api/api.yaml b/api/api.yaml index 4295b62..b18e4f2 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -80,7 +80,7 @@ paths: content: application/json: schema: - $ref: '../models/graph.yaml#/components/schemas/AnnotateRequest' + $ref: '../models/annotation.yaml#/components/schemas/Annotation' responses: '200': $ref: '../models/request.yaml#/components/responses/Success' diff --git a/artifacts/infragraph.proto b/artifacts/infragraph.proto index 836b2bd..301d16d 100644 --- a/artifacts/infragraph.proto +++ b/artifacts/infragraph.proto @@ -481,67 +481,6 @@ message GraphContent { optional string networkx = 2; } -// Defines an annotation to be applied to a graph node. -message AnnotationNode { - - // A fully qualified node name that MUST exist in the graph. - // - server.0.xpu.0 - // - server.6.nic.3 - // - switch.2.asic.0 - optional string name = 1; - - // The attribute name that will be added to the endpoint. - optional string attribute = 2; - - // The attribute value that will be added to the endpoint. - optional string value = 3; -} - -// Defines an annotation to be applied to a graph edge. -message AnnotationEdge { - - // A fully qualified endpoint name that MUST exist as part of an edge along with ep2 - // in the graph. - // - server.0.xpu.0 - // - server.6.nic.3 - // - switch.2.asic.0 - optional string ep1 = 1; - - // A fully qualified endpoint name that MUST exist as part of an edge along with ep1 - // in the graph. - // - server.0.xpu.0 - // - server.6.nic.3 - // - switch.2.asic.0 - optional string ep2 = 2; - - // The attribute name that will be added to the edge. - optional string attribute = 3; - - // The attribute value that will be added to the edge. - optional string value = 4; -} - -// Request object specifying which graph elements (nodes or edges) to annotate and the -// annotations to apply. -message AnnotateRequest { - - message Choice { - enum Enum { - unspecified = 0; - nodes = 1; - edges = 2; - } - } - // Select whether to annotate nodes or edges.. - optional Choice.Enum choice = 1; - - // List of node annotations to apply when the choice is set to nodes. - repeated AnnotationNode nodes = 2; - - // List of edge annotations to apply when the choice is set to edges. - repeated AnnotationEdge edges = 3; -} - // Represents a generic name and value pair attribute. message NameValue { @@ -717,6 +656,75 @@ message QueryRequest { repeated QueryShortestPathFilter shortest_path_filters = 3; } +// Defines an annotation to be applied to a graph node. +message AnnotationAttribute { + + // The attribute name that will be added to the endpoint. + optional string attribute = 1; + + // The attribute value that will be added to the endpoint. + optional string value = 2; +} + +// Defines an annotation to be applied to a graph node. +message AnnotationNode { + + // A fully qualified node name that MUST exist in the graph. + // - server.0.xpu.0 + // - server.6.nic.3 + // - switch.2.asic.0 + optional string name = 1; + + // An inventory of attributes and values. + repeated AnnotationAttribute attributes = 2; +} + +// Defines an annotation to be applied to a graph link. +message AnnotationLink { + + // A fully qualified link name that MUST exist in the graph. + // - pcie + optional string name = 1; + + // An inventory of attributes and values. + repeated AnnotationAttribute attributes = 2; +} + +// Defines an annotation to be applied to a graph edge. +message AnnotationEdge { + + // A fully qualified endpoint name that MUST exist as part of an edge along with ep2 + // in the graph. + // - server.0.xpu.0 + // - server.6.nic.3 + // - switch.2.asic.0 + optional string ep1 = 1; + + // A fully qualified endpoint name that MUST exist as part of an edge along with ep1 + // in the graph. + // - server.0.xpu.0 + // - server.6.nic.3 + // - switch.2.asic.0 + optional string ep2 = 2; + + // An inventory of attributes and values. + repeated AnnotationAttribute attributes = 3; +} + +// Request object specifying which graph elements (nodes or edges) to annotate and the +// annotations to apply. +message Annotation { + + // List of annotations to apply to nodes. + repeated AnnotationNode nodes = 1; + + // List of annotations to apply to edges. + repeated AnnotationEdge edges = 2; + + // List of annotations to apply to links. + repeated AnnotationLink links = 3; +} + // Version details message Version { @@ -785,7 +793,7 @@ message QueryGraphResponse { message AnnotateGraphRequest { - AnnotateRequest annotate_request = 1; + Annotation annotation = 1; } message AnnotateGraphResponse { Warning warning = 1; diff --git a/artifacts/openapi.yaml b/artifacts/openapi.yaml index 3ef5b13..c468202 100644 --- a/artifacts/openapi.yaml +++ b/artifacts/openapi.yaml @@ -113,7 +113,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AnnotateRequest' + $ref: '#/components/schemas/Annotation' responses: '200': $ref: '#/components/responses/Success' @@ -776,92 +776,6 @@ components: This contains the returned graph content formatted as a networkx yaml string. type: string x-field-uid: 2 - Annotation.Node: - description: |- - Defines an annotation to be applied to a graph node. - type: object - properties: - name: - description: |- - A fully qualified node name that MUST exist in the graph. - - server.0.xpu.0 - - server.6.nic.3 - - switch.2.asic.0 - type: string - x-field-uid: 1 - attribute: - description: |- - The attribute name that will be added to the endpoint. - type: string - x-field-uid: 2 - value: - description: |- - The attribute value that will be added to the endpoint. - type: string - x-field-uid: 3 - Annotation.Edge: - description: |- - Defines an annotation to be applied to a graph edge. - type: object - properties: - ep1: - description: |- - A fully qualified endpoint name that MUST exist as part of an edge along with ep2 in the graph. - - server.0.xpu.0 - - server.6.nic.3 - - switch.2.asic.0 - type: string - x-field-uid: 1 - ep2: - description: |- - A fully qualified endpoint name that MUST exist as part of an edge along with ep1 in the graph. - - server.0.xpu.0 - - server.6.nic.3 - - switch.2.asic.0 - type: string - x-field-uid: 2 - attribute: - description: |- - The attribute name that will be added to the edge. - type: string - x-field-uid: 3 - value: - description: |- - The attribute value that will be added to the edge. - type: string - x-field-uid: 4 - AnnotateRequest: - description: |- - Request object specifying which graph elements (nodes or edges) to annotate and the annotations to apply. - type: object - properties: - choice: - description: |- - Select whether to annotate nodes or edges.. - type: string - x-field-uid: 1 - x-enum: - nodes: - x-field-uid: 1 - edges: - x-field-uid: 2 - enum: - - nodes - - edges - nodes: - description: |- - List of node annotations to apply when the choice is set to nodes. - type: array - items: - $ref: '#/components/schemas/Annotation.Node' - x-field-uid: 2 - edges: - type: array - description: |- - List of edge annotations to apply when the choice is set to edges. - items: - $ref: '#/components/schemas/Annotation.Edge' - x-field-uid: 3 NameValue: description: |- Represents a generic name and value pair attribute. @@ -1123,6 +1037,113 @@ components: x-status: status: under_review information: Proposal to abstract the shortest path interface to the graph. + Annotation.Attribute: + description: |- + Defines an annotation to be applied to a graph node. + type: object + properties: + attribute: + description: |- + The attribute name that will be added to the endpoint. + type: string + x-field-uid: 1 + value: + description: |- + The attribute value that will be added to the endpoint. + type: string + x-field-uid: 2 + Annotation.Node: + description: |- + Defines an annotation to be applied to a graph node. + type: object + properties: + name: + description: |- + A fully qualified node name that MUST exist in the graph. + - server.0.xpu.0 + - server.6.nic.3 + - switch.2.asic.0 + type: string + x-field-uid: 1 + attributes: + description: |- + An inventory of attributes and values. + type: array + items: + $ref: '#/components/schemas/Annotation.Attribute' + x-field-uid: 2 + Annotation.Link: + description: |- + Defines an annotation to be applied to a graph link. + type: object + properties: + name: + description: |- + A fully qualified link name that MUST exist in the graph. + - pcie + type: string + x-field-uid: 1 + attributes: + description: |- + An inventory of attributes and values. + type: array + items: + $ref: '#/components/schemas/Annotation.Attribute' + x-field-uid: 2 + Annotation.Edge: + description: |- + Defines an annotation to be applied to a graph edge. + type: object + properties: + ep1: + description: |- + A fully qualified endpoint name that MUST exist as part of an edge along with ep2 in the graph. + - server.0.xpu.0 + - server.6.nic.3 + - switch.2.asic.0 + type: string + x-field-uid: 1 + ep2: + description: |- + A fully qualified endpoint name that MUST exist as part of an edge along with ep1 in the graph. + - server.0.xpu.0 + - server.6.nic.3 + - switch.2.asic.0 + type: string + x-field-uid: 2 + attributes: + description: |- + An inventory of attributes and values. + type: array + items: + $ref: '#/components/schemas/Annotation.Attribute' + x-field-uid: 3 + Annotation: + description: |- + Request object specifying which graph elements (nodes or edges) to annotate and the annotations to apply. + type: object + properties: + nodes: + description: |- + List of annotations to apply to nodes. + type: array + items: + $ref: '#/components/schemas/Annotation.Node' + x-field-uid: 1 + edges: + type: array + description: |- + List of annotations to apply to edges. + items: + $ref: '#/components/schemas/Annotation.Edge' + x-field-uid: 2 + links: + type: array + description: |- + List of annotations to apply to links. + items: + $ref: '#/components/schemas/Annotation.Link' + x-field-uid: 3 Version: description: |- Version details diff --git a/models/annotation.yaml b/models/annotation.yaml index dbb1099..11a7ebb 100644 --- a/models/annotation.yaml +++ b/models/annotation.yaml @@ -1,83 +1,101 @@ components: schemas: - Annotation.NodeNameValue: + Annotation.Attribute: + description: Defines an annotation to be applied to a graph node. type: object properties: - id: + attribute: + description: |- + The attribute name that will be added to the endpoint. type: string x-field-uid: 1 - name: - type: string - x-field-uid: 2 value: + description: |- + The attribute value that will be added to the endpoint. type: string - x-field-uid: 3 - Annotation.Freeform: + x-field-uid: 2 + Annotation.Node: + description: Defines an annotation to be applied to a graph node. type: object properties: - choice: + name: + description: |- + A fully qualified node name that MUST exist in the graph. + - server.0.xpu.0 + - server.6.nic.3 + - switch.2.asic.0 type: string x-field-uid: 1 - x-enum: - json: - x-field-uid: 1 - yaml: - x-field-uid: 2 - protobuf: - x-field-uid: 3 - json: + attributes: description: |- - Annotation data serialized as a json string - type: string + An inventory of attributes and values. + type: array + items: + $ref: "#/components/schemas/Annotation.Attribute" x-field-uid: 2 - yaml: + Annotation.Link: + description: Defines an annotation to be applied to a graph link. + type: object + properties: + name: description: |- - Annotation data serialized as a yaml string + A fully qualified link name that MUST exist in the graph. + - pcie type: string - x-field-uid: 3 - protobuf: + x-field-uid: 1 + attributes: description: |- - Annotation data serialized as a protobuf string and encoded as base64. - type: string - x-field-uid: 4 - Annotation: + An inventory of attributes and values. + type: array + items: + $ref: "#/components/schemas/Annotation.Attribute" + x-field-uid: 2 + Annotation.Edge: + description: Defines an annotation to be applied to a graph edge. type: object - required: - - name properties: - name: + ep1: description: |- - An alias for this annotation. + A fully qualified endpoint name that MUST exist as part of an edge along with ep2 in the graph. + - server.0.xpu.0 + - server.6.nic.3 + - switch.2.asic.0 type: string x-field-uid: 1 - description: + ep2: description: |- - A detailed description of the structure of the - information contained in the freeform property. + A fully qualified endpoint name that MUST exist as part of an edge along with ep1 in the graph. + - server.0.xpu.0 + - server.6.nic.3 + - switch.2.asic.0 type: string x-field-uid: 2 - choice: - type: string - x-field-uid: 3 - x-enum: - freeform: - x-field-uid: 1 - nodes: - x-field-uid: 2 - freeform: + attributes: description: |- - Annotation data. - $ref: "#/components/schemas/Annotation.Freeform" - x-field-uid: 4 + An inventory of attributes and values. + type: array + items: + $ref: "#/components/schemas/Annotation.Attribute" + x-field-uid: 3 + Annotation: + description: Request object specifying which graph elements (nodes or edges) to annotate and the annotations to apply. + type: object + properties: nodes: - description: |- - Add information to specific fully qualified node names in the infrastructure graph. - - a fully qualified node name is of the form: - - device_name.device_index.component_name.component_index - - examples: - - nodes.add(id="dgx.0.xpu.0", name="rank", value="0") - - nodes.add(id="dgx.0.xpu.1", name="rank", value="1")) + description: List of annotations to apply to nodes. + type: array + items: + $ref: "#/components/schemas/Annotation.Node" + x-field-uid: 1 + edges: + type: array + description: List of annotations to apply to edges. + items: + $ref: "#/components/schemas/Annotation.Edge" + x-field-uid: 2 + links: type: array + description: List of annotations to apply to links. items: - $ref: "#/components/schemas/Annotation.NodeNameValue" - x-field-uid: 5 + $ref: "#/components/schemas/Annotation.Link" + x-field-uid: 3 \ No newline at end of file diff --git a/models/graph.yaml b/models/graph.yaml index 121d13e..34f667e 100644 --- a/models/graph.yaml +++ b/models/graph.yaml @@ -28,83 +28,6 @@ components: This contains the returned graph content formatted as a networkx yaml string. type: string x-field-uid: 2 - Annotation.Node: - description: Defines an annotation to be applied to a graph node. - type: object - properties: - name: - description: |- - A fully qualified node name that MUST exist in the graph. - - server.0.xpu.0 - - server.6.nic.3 - - switch.2.asic.0 - type: string - x-field-uid: 1 - attribute: - description: |- - The attribute name that will be added to the endpoint. - type: string - x-field-uid: 2 - value: - description: |- - The attribute value that will be added to the endpoint. - type: string - x-field-uid: 3 - Annotation.Edge: - description: Defines an annotation to be applied to a graph edge. - type: object - properties: - ep1: - description: |- - A fully qualified endpoint name that MUST exist as part of an edge along with ep2 in the graph. - - server.0.xpu.0 - - server.6.nic.3 - - switch.2.asic.0 - type: string - x-field-uid: 1 - ep2: - description: |- - A fully qualified endpoint name that MUST exist as part of an edge along with ep1 in the graph. - - server.0.xpu.0 - - server.6.nic.3 - - switch.2.asic.0 - type: string - x-field-uid: 2 - attribute: - description: |- - The attribute name that will be added to the edge. - type: string - x-field-uid: 3 - value: - description: |- - The attribute value that will be added to the edge. - type: string - x-field-uid: 4 - AnnotateRequest: - description: Request object specifying which graph elements (nodes or edges) to annotate and the annotations to apply. - type: object - properties: - choice: - description: Select whether to annotate nodes or edges.. - type: string - x-field-uid: 1 - x-enum: - nodes: - x-field-uid: 1 - edges: - x-field-uid: 2 - nodes: - description: List of node annotations to apply when the choice is set to nodes. - type: array - items: - $ref: "#/components/schemas/Annotation.Node" - x-field-uid: 2 - edges: - type: array - description: List of edge annotations to apply when the choice is set to edges. - items: - $ref: "#/components/schemas/Annotation.Edge" - x-field-uid: 3 responses: GraphResponse: description: |- diff --git a/src/infragraph/infragraph_service.py b/src/infragraph/infragraph_service.py index ea434e9..07c44c9 100644 --- a/src/infragraph/infragraph_service.py +++ b/src/infragraph/infragraph_service.py @@ -13,6 +13,7 @@ from networkx.readwrite import json_graph import re import yaml +from itertools import product as iterproduct from infragraph import * @@ -46,6 +47,7 @@ def __init__(self): super().__init__() self._graph: Graph = Graph() self._device_data = {} + self._graph_node_prefix_map = {} self._infrastructure: Infrastructure = Infrastructure() @property @@ -64,6 +66,63 @@ def get_networkx_graph(self) -> Graph: raise ValueError("The networkx graph has not been created. Please call set_graph() first.") return self._graph + def _expand_node_string(self, s: str) -> List[str]: + """Expand a device/component string with slice notation into dot-notation paths. + + Format: name[start:stop]name[start:stop]... + Each [start:stop] expands like range(start, stop). Segments without a slice + are kept as-is. Results are the cartesian product of all segment expansions, + joined with '.'. + + Examples: + "dgx" -> ["dgx"] + "dgx[0:3]" -> ["dgx.0", "dgx.1", "dgx.2"] + "dgx[0:2]cpu[0:2]" -> ["dgx.0.cpu.0", "dgx.0.cpu.1", + "dgx.1.cpu.0", "dgx.1.cpu.1"] + """ + if not s: + return [] + + # Parse the input string into (name, start, stop) tuples. + # The regex matches a name (alphanumeric/underscore/hyphen) optionally + # followed by a slice in the form [start:stop]. + # If no slice is present, start and stop are empty strings. + pattern = r'([A-Za-z0-9_-]+)(?:\[(\d+):(\d+)\])?' + matches = re.findall(pattern, s) + + if not matches: + return [s] + + # For each matched segment, build a list of expanded strings. + # A segment with a slice expands to: ["name.0", "name.1", ..., "name.(stop-1)"] + # A segment without a slice expands to just: ["name"] + segments: List[List[str]] = [] + + for match in matches: + name = match[0] + start = match[1] + stop = match[2] + + if start and stop: + # Expand the slice range into individual indexed entries + expanded = [] + for i in range(int(start), int(stop)): + expanded.append(name + "." + str(i)) + segments.append(expanded) + else: + # No slice — segment is a single plain name + segments.append([name]) + + # Compute the cartesian product across all segments and join with '.' + # e.g. ["dgx.0", "dgx.1"] x ["cpu.0", "cpu.1"] + # -> ["dgx.0.cpu.0", "dgx.0.cpu.1", "dgx.1.cpu.0", "dgx.1.cpu.1"] + result: List[str] = [] + + for combo in iterproduct(*segments): + result.append(".".join(combo)) + + return result + def _expand_device_endpoint( self, device_name: str, @@ -309,7 +368,7 @@ def _generate_device_data(self): """ self._parse_device_components() - self._parse_device_edges() + self._parse_device_edges() def _generate_device_nodes(self, instance_name: str, device_name: str): """ @@ -527,6 +586,7 @@ def set_graph(self, payload: Union[str, Infrastructure]) -> None: self._validate_device_edges() self._parse_infrastructure_edges() self._validate_graph() + self._build_prefix_map() def _validate_device_edges(self): """Ensure that there are no edges between device instances @@ -609,15 +669,76 @@ def get_endpoints(self, name: str, value: Optional[str] = None) -> List[str]: endpoints.append(node) return endpoints - def annotate_graph(self, payload: Union[str, AnnotateRequest]): + def _build_prefix_map(self): + """Build a prefix-to-nodes lookup map from all nodes in the graph. + + Each graph node is a dot-separated path (e.g. "dgx.0.cpu.1.port.2"). + This method indexes every prefix of that path so that a caller can + look up all nodes that live under a given prefix without scanning the + full node list each time. + + Example: + Node "dgx.0.cpu.1" produces three entries: + "dgx" -> [..., "dgx.0.cpu.1"] + "dgx.0" -> [..., "dgx.0.cpu.1"] + "dgx.0.cpu.1" -> [..., "dgx.0.cpu.1"] + + The result is stored in `self._graph_node_prefix_map` as a + dict[str, List[str]], where each key is a prefix and the value is + the list of fully qualified node names that share that prefix. + + This map is consumed by lookups that resolve a partial node name + (e.g. "dgx.0") to all of its descendants in the graph. + """ + + for node in self._graph.nodes: + parts = node.split(".") + for i in range(1, len(parts) + 1): + prefix = ".".join(parts[:i]) + self._graph_node_prefix_map.setdefault(prefix, []).append(node) + + def annotate_graph(self, payload: Union[str, Annotation]): """Annotation the graph using the data provided in the payload""" if isinstance(payload, str): - annotate_request = AnnotateRequest().deserialize(payload) + annotate_request = Annotation().deserialize(payload) else: - annotate_request: AnnotateRequest = payload + annotate_request: Annotation = payload + for annotation_node in annotate_request.nodes: - endpoint = self._graph.nodes[annotation_node.name] - endpoint[annotation_node.attribute] = annotation_node.value + # expand the nodes + nodes = self._expand_node_string(annotation_node.name) + matched = set() + for node in nodes: + matched.update(self._graph_node_prefix_map.get(node, [])) + + for attribute_kvp in annotation_node.attributes: + networkx.set_node_attributes(self._graph, {n: {attribute_kvp.attribute: attribute_kvp.value} for n in matched}) + + # edges + for annotation_node in annotate_request.edges: + # expand the nodes + source_edges = self._expand_node_string(annotation_node.ep1) + destination_edges = self._expand_node_string(annotation_node.ep2) + + matched_edges = [] + adj = {n: set(self._graph.neighbors(n)) for n in self._graph.nodes} + for s in source_edges: + # only check neighbors of s (not all edges) + for d in adj[s]: + if d in destination_edges: + matched_edges.append((s, d)) + + for attribute_kvp in annotation_node.attributes: + networkx.set_edge_attributes(self._graph, {(u, v): {attribute_kvp.attribute: attribute_kvp.value} for u, v in matched_edges}) + + # links + for annotation_link in annotate_request.links: + for _, _, data in self._graph.edges(data=True): + link_name = data.get("link") + if link_name in annotation_link.name: + for link_annotation in annotation_link.attributes: + data.update(link_annotation.attribute[link_annotation.value]) + def query_graph(self, payload: Union[str, QueryRequest]) -> QueryResponseContent: """Query the graph""" diff --git a/src/tests/test_expand_node_string.py b/src/tests/test_expand_node_string.py new file mode 100644 index 0000000..1b41978 --- /dev/null +++ b/src/tests/test_expand_node_string.py @@ -0,0 +1,76 @@ +import pytest +from infragraph.infragraph_service import InfraGraphService + + +@pytest.fixture +def service(): + return InfraGraphService() + + +def test_device_only(service): + """device — no slice, single name returned as-is""" + result = service._expand_node_string("dgx") + assert result == ["dgx"] + + +def test_device_with_slice(service): + """device[] — one level with slice""" + result = service._expand_node_string("dgx[0:3]") + assert result == ["dgx.0", "dgx.1", "dgx.2"] + + +def test_device_component_no_component_slice(service): + """device[]component — device has slice, component has no slice""" + result = service._expand_node_string("dgx[0:2]cpu") + assert result == ["dgx.0.cpu", "dgx.1.cpu"] + + +def test_device_component_with_slices(service): + """device[]component[] — both levels have slices""" + result = service._expand_node_string("dgx[0:2]cpu[0:2]") + assert result == [ + "dgx.0.cpu.0", + "dgx.0.cpu.1", + "dgx.1.cpu.0", + "dgx.1.cpu.1", + ] + + +def test_device_two_components_last_no_slice(service): + """device[]component[]component — two components, last has no slice""" + result = service._expand_node_string("dgx[0:2]cpu[0:2]port") + assert result == [ + "dgx.0.cpu.0.port", + "dgx.0.cpu.1.port", + "dgx.1.cpu.0.port", + "dgx.1.cpu.1.port", + ] + + +def test_device_two_components_all_slices(service): + """device[]component[]component[] — all three levels have slices""" + result = service._expand_node_string("dgx[0:2]cpu[0:2]port[0:3]") + assert result == [ + "dgx.0.cpu.0.port.0", + "dgx.0.cpu.0.port.1", + "dgx.0.cpu.0.port.2", + "dgx.0.cpu.1.port.0", + "dgx.0.cpu.1.port.1", + "dgx.0.cpu.1.port.2", + "dgx.1.cpu.0.port.0", + "dgx.1.cpu.0.port.1", + "dgx.1.cpu.0.port.2", + "dgx.1.cpu.1.port.0", + "dgx.1.cpu.1.port.1", + "dgx.1.cpu.1.port.2", + ] + + +def test_empty_string(service): + """empty input returns empty list""" + result = service._expand_node_string("") + assert result == [] + + +if __name__ == "__main__": + pytest.main(["-s", __file__]) diff --git a/src/tests/test_ipaddress_annotations.py b/src/tests/test_ipaddress_annotations.py index 6eebe50..8152c46 100644 --- a/src/tests/test_ipaddress_annotations.py +++ b/src/tests/test_ipaddress_annotations.py @@ -23,14 +23,13 @@ async def test_ipaddress_annotations(): print(nic_response.node_matches) # annotate the graph - annotate_request = AnnotateRequest() + annotation = Annotation() for idx, match in enumerate(nic_response.node_matches): - annotate_request.nodes.add( - name=match.id, - attribute="ipaddress", - value=str(ipaddress.ip_address(idx)), + annotation_node = annotation.nodes.add( + name=match.id ) - service.annotate_graph(annotate_request) + annotation_node.attributes.add(attribute="ipaddress", value=str(ipaddress.ip_address(idx))) + service.annotate_graph(annotation) # query the graph for ipaddress attributes ipaddress_request = QueryRequest() @@ -44,8 +43,8 @@ async def test_ipaddress_annotations(): # validation assert len(nic_response.node_matches) > 0 - assert len(nic_response.node_matches) == len(annotate_request.nodes) - assert len(annotate_request.nodes) == len(ipaddress_response.node_matches) + assert len(nic_response.node_matches) == len(annotation.nodes) + assert len(annotation.nodes) == len(ipaddress_response.node_matches) if __name__ == "__main__": diff --git a/src/tests/test_rank_annotations.py b/src/tests/test_rank_annotations.py index df8c948..d7f7dbc 100644 --- a/src/tests/test_rank_annotations.py +++ b/src/tests/test_rank_annotations.py @@ -19,11 +19,13 @@ async def test_rank_annotations(): filter.id_filter.value = r"host\.\d+\.xpu\.\d+" npu_response = service.query_graph(npu_request) - # annotate the graph - annotate_request = AnnotateRequest() + annotation = Annotation() for idx, match in enumerate(npu_response.node_matches): - annotate_request.nodes.add(name=match.id, attribute="rank", value=str(idx)) - service.annotate_graph(annotate_request) + annotation_node = annotation.nodes.add( + name=match.id + ) + annotation_node.attributes.add(attribute="rank", value=str(idx)) + service.annotate_graph(annotation.serialize()) # query the graph for rank attributes rank_request = QueryRequest() @@ -36,8 +38,8 @@ async def test_rank_annotations(): # validation assert len(npu_response.node_matches) > 0 - assert len(npu_response.node_matches) == len(annotate_request.nodes) - assert len(annotate_request.nodes) == len(rank_response.node_matches) + assert len(npu_response.node_matches) == len(annotation.nodes) + assert len(annotation.nodes) == len(rank_response.node_matches) if __name__ == "__main__": diff --git a/src/tests/test_shortest_path.py b/src/tests/test_shortest_path.py index e870d1c..e670b0e 100644 --- a/src/tests/test_shortest_path.py +++ b/src/tests/test_shortest_path.py @@ -14,10 +14,13 @@ async def test_shortest_path(ranks: Tuple[int, int]): # add ranks npu_endpoints = service.get_endpoints("type", Component.XPU) - annotate_request = AnnotateRequest() + annotation = Annotation() for idx, npu_endpoint in enumerate(npu_endpoints): - annotate_request.nodes.add(name=npu_endpoint, attribute="rank", value=str(idx)) - service.annotate_graph(annotate_request.serialize()) + annotation_node = annotation.nodes.add( + name=npu_endpoint + ) + annotation_node.attributes.add(attribute="rank", value=str(idx)) + service.annotate_graph(annotation.serialize()) # find shortest path from one rank to another src_endpoint = service.get_endpoints("rank", str(ranks[0]))[0] From 903f4f73cc65c0f85770fe5ce1626bcd51614274 Mon Sep 17 00:00:00 2001 From: Github Actions Bot Date: Tue, 5 May 2026 07:21:01 +0000 Subject: [PATCH 02/24] Update auto generated content --- docs/src/openapi.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/openapi.html b/docs/src/openapi.html index 7826b7d..0b06d1b 100644 --- a/docs/src/openapi.html +++ b/docs/src/openapi.html @@ -576,13 +576,13 @@

Contributions

-
Request Body schema: application/json
choice
string
Enum: "nodes" "edges"

Select whether to annotate nodes or edges..

-
Array of objects (Annotation.Node)

List of node annotations to apply when the choice is set to nodes.

-
Array of objects (Annotation.Edge)

List of edge annotations to apply when the choice is set to edges.

+
Request Body schema: application/json
Array of objects (Annotation.Node)

List of annotations to apply to nodes.

+
Array of objects (Annotation.Edge)

List of annotations to apply to edges.

+
Array of objects (Annotation.Link)

List of annotations to apply to links.

Responses

Request samples

Content type
application/json
{
  • "choice": "nodes",
  • "nodes": [
    ],
  • "edges": [
    ]
}

Response samples

Content type
application/json
{
  • "warnings": [
    ]
}

Actions

query_graph

Query the current fully expanded graph using a declarative query syntax.

+

Request samples

Content type
application/json
{
  • "nodes": [
    ],
  • "edges": [
    ],
  • "links": [
    ]
}

Response samples

Content type
application/json
{
  • "warnings": [
    ]
}

Actions

query_graph

Query the current fully expanded graph using a declarative query syntax.

  • example: Ask the graph for specific endpoints such as those of type XPU or NIC
@@ -596,7 +596,7 @@

Contributions

Response samples

Content type
application/json
{
  • "api_spec_version": "",
  • "sdk_version": "",
  • "app_version": ""
}