Skip to content

Commit 7a6dee0

Browse files
authored
feat(epp): add topology-extractor datalayer plugin (#1678)
* feat(epp): add topology-extractor datalayer plugin Introduces a topology-extractor endpoint extractor that stamps each endpoint with a Topology attribute (hostname) at creation time. The hostname is resolved once when the endpoint is created: - Default: the Pod hostname field (EndpointMetadata.PodName). - With hostnameLabel configured: the value of the named pod label. If the label is absent, the attribute is not set. Works for both k8s-discovered and file-based endpoints. The attribute is a static Cloneable value stored in the endpoint's AttributeMap. The extractor self-registers with the endpoint-notification-source, creating a default source if none is configured. Planned scorer (not implemented): topology-locality-scorer would score 1.0 for endpoints whose Topology.Hostname matches a key set on the request attribute store, and 0.0 otherwise. The request key would be published by a DataProducer reading a request header (e.g. x-topology-key) or from EPP node metadata. When no key is present on the request, all endpoints score 0.0. Signed-off-by: Etai Lev Ran <elevran@gmail.com> * feat(epp): register topology-extractor for both Pod and Endpoint notifications - Remove auto-generated release note fragment (generated by CI action) - Register for both the endpoint notification source and a Pod k8s notification source via RegisterDependencies - With hostnameLabel configured: endpoint handler extracts the label value and stamps the Topology attribute; Pod handler is a no-op - Without hostnameLabel: endpoint handler tracks the live Endpoint in an internal map; Pod notification handler reads spec.hostname from the Pod object and stamps the matching endpoint - Maintain the endpoint map under a RWMutex; remove entries on delete The prior implementation only handled endpoint events, which do not carry the full Pod object. Pod notifications provide spec.hostname for the no-label path. Signed-off-by: Etai Lev Ran <elevran@gmail.com> * fix(epp): handle multi-port and pod-before-endpoint ordering in topology-extractor The prior implementation keyed endpoint lookup by endpoint NamespacedName (e.g. worker-1-rank-0), which never matches the pod notification key (worker-1). Also, pod notifications fire before endpoints are created, so the attribute was never stamped. Fix: - Key both internal maps by pod identity {PodName, Namespace}. - endpoints map holds a []Endpoint per pod to cover all rank entries. - hostnames map caches spec.hostname from pod notifications; whichever event fires first, the attribute is written once both have been seen. - Only cache hostnames for ready pods; evict on not-ready or pod delete. Signed-off-by: Etai Lev Ran <elevran@gmail.com> * docs/test: update README and fix lint in topology-extractor README: remove stale 'at endpoint creation' phrasing; clarify that Hostname is sourced from spec.hostname or a configured pod label. Tests: drop always-constant parameters from helper functions (unparam); introduce constants for repeated string literals (goconst). Signed-off-by: Etai Lev Ran <elevran@gmail.com> * feat(epp): extend topology-extractor with zone and region Add zone and region fields to the Topology attribute and the params struct. Each param names the pod label to read; defaults are the standard Kubernetes topology labels (corev1.LabelHostname, LabelTopologyZone, LabelTopologyRegion). The hostname label falling back to spec.hostname is preserved: when the hostname label is absent the endpoint is tracked and stamped once the Pod notification fires. Zone and region have no fallback. Zone and region values are not read from Node objects -- that would require RBAC for Node GET/LIST. They are populated when the pod itself carries the corresponding labels. Signed-off-by: Etai Lev Ran <elevran@gmail.com> * fix(topology-extractor): idempotent endpoint tracking and README wording Change endpoints map inner type from []Endpoint to map[NamespacedName]Endpoint. EventAddOrUpdate re-fires on endpoint updates, so the previous append caused duplicate entries for the same endpoint. Map assignment is idempotent. Remove the now-unused slices import. Fix README wording: "pod label" -> "endpoint label". Signed-off-by: Etai Lev Ran <elevran@gmail.com> * feat(epp): add Rack attribute to topology-extractor Adds a Rack locality field between Hostname and Zone on the Topology attribute. Sourced from topology.kubernetes.io/rack by default, configurable via the params.rack plugin parameter. Mirrors the existing Hostname handling for the no-fallback path: on subsequent pod notifications the previously stamped Rack value from the endpoint event is preserved alongside Zone and Region. Signed-off-by: Etai Lev Ran <elevran@gmail.com> --------- Signed-off-by: Etai Lev Ran <elevran@gmail.com>
1 parent 04d683f commit 7a6dee0

5 files changed

Lines changed: 1030 additions & 0 deletions

File tree

cmd/epp/runner/runner.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,12 @@ import (
6565
attrmodels "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/datalayer/attribute/models"
6666
attrprefix "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/datalayer/attribute/prefix"
6767
attrsession "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/datalayer/attribute/session"
68+
attrtopology "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/datalayer/attribute/topology"
6869
discoveryfile "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/datalayer/discovery/file"
6970
extdcgm "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/datalayer/extractor/dcgm"
7071
extractormetrics "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/datalayer/extractor/metrics"
7172
extmodels "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/datalayer/extractor/models"
73+
exttopology "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/datalayer/extractor/topology"
7274
srcdcgm "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/datalayer/source/dcgm"
7375
sourcemetrics "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/datalayer/source/metrics"
7476
srcmodels "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/datalayer/source/models"
@@ -566,6 +568,7 @@ func (r *Runner) registerInTreePlugins() {
566568
// data layer models source/extractor
567569
fwkplugin.Register(srcmodels.ModelsDataSourceType, srcmodels.ModelDataSourceFactory)
568570
fwkplugin.Register(attrmodels.ModelsExtractorType, extmodels.ModelServerExtractorFactory)
571+
fwkplugin.Register(attrtopology.TopologyExtractorType, exttopology.Factory)
569572

570573
// data layer DCGM source/extractor
571574
fwkplugin.Register(srcdcgm.DCGMDataSourceType, srcdcgm.DCGMDataSourceFactory)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Topology Attributes
2+
3+
This package defines the data structures for endpoint topology information,
4+
used by topology-aware routing plugins.
5+
6+
## `Topology`
7+
8+
Carries the locality of an endpoint. Populated once at endpoint creation.
9+
10+
- **Key**: `TopologyAttributeKey` (`Topology`)
11+
- **Fields**:
12+
- `Hostname`: The host name of the endpoint. Sourced from `spec.hostname`
13+
on the Pod object, or from a user-configured pod label.
14+
- `Rack`: The failure domain rack of the endpoint. Sourced from a
15+
user-configured pod label.
16+
- `Zone`: The failure domain zone of the endpoint. Sourced from a
17+
user-configured pod label.
18+
- `Region`: The geographic region of the endpoint. Sourced from a
19+
user-configured pod label.
20+
21+
## Producers
22+
23+
The following plugins produce this attribute:
24+
25+
- **`topology-extractor`** (Data Layer): Sets the `Topology` attribute using
26+
`spec.hostname` from the Pod object, or the value of a configured endpoint label.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
Copyright 2026 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package topology declares the Topology attribute that carries endpoint
18+
// locality information for topology-aware routing.
19+
package topology
20+
21+
import (
22+
fwkdl "github.com/llm-d/llm-d-router/pkg/epp/framework/interface/datalayer"
23+
"github.com/llm-d/llm-d-router/pkg/epp/framework/interface/plugin"
24+
)
25+
26+
const (
27+
// TopologyExtractorType is the plugin type identifier for the topology extractor.
28+
TopologyExtractorType = "topology-extractor"
29+
)
30+
31+
// TopologyAttributeKey identifies the Topology attribute stored on each endpoint.
32+
var TopologyAttributeKey = plugin.NewDataKey("Topology", TopologyExtractorType)
33+
34+
// Topology carries the locality information for an endpoint.
35+
// Fields are populated from pod labels at endpoint creation time.
36+
// Hostname falls back to spec.hostname when the configured label is absent.
37+
type Topology struct {
38+
// Hostname identifies the node on which the endpoint runs.
39+
Hostname string
40+
// Rack identifies the failure domain rack of the endpoint.
41+
Rack string
42+
// Zone identifies the failure domain zone of the endpoint.
43+
Zone string
44+
// Region identifies the geographic region of the endpoint.
45+
Region string
46+
}
47+
48+
// Clone returns an independent copy of the Topology.
49+
func (t *Topology) Clone() fwkdl.Cloneable {
50+
if t == nil {
51+
return nil
52+
}
53+
cp := *t
54+
return &cp
55+
}

0 commit comments

Comments
 (0)