Skip to content

Commit eac2206

Browse files
kvapsclaude
andcommitted
feat(api): typed Node.Spec.SatelliteEndpoint (Phase 10.3 step)
Replaces the upstream-LINSTOR-shaped `Spec.Props["SatelliteEndpoint"]` with a typed Node.Spec.SatelliteEndpoint string field. The dispatcher's lookupEndpoint now reads typed first and falls back to the legacy Props key — a partially-migrated cluster (or a satellite Hello that still pushes the prop) keeps working. The k8s store transcodes both ways: wireToCRDNodeSpec lifts the incoming Props key into the typed slot on Create/Update; crdToWireNode re-emits it back into Props on GET so golinstor sees the unchanged shape. The whole field becomes irrelevant once Phase 10.6 lands and the satellite gRPC dial is gone — but until then this kills one more PropsContainers anti-pattern site. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
1 parent 3e78015 commit eac2206

4 files changed

Lines changed: 59 additions & 10 deletions

File tree

api/v1alpha1/node_types.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ type NodeSpec struct {
4848
// The first interface is treated as the satellite endpoint.
4949
// +optional
5050
NetInterfaces []NodeNetInterface `json:"netInterfaces,omitempty"`
51+
52+
// satelliteEndpoint is the controller→satellite gRPC endpoint
53+
// (`host:port`). Phase 10.3: replaces `Props["SatelliteEndpoint"]`
54+
// — the dispatcher reads this typed field first, falling back to
55+
// the props bag for forward-compat with pre-migration data. The
56+
// field becomes irrelevant once Phase 10.6 lands and the gRPC
57+
// path is gone.
58+
// +optional
59+
SatelliteEndpoint string `json:"satelliteEndpoint,omitempty"`
5160
}
5261

5362
// NodeNetInterface is one advertised endpoint of a satellite.

config/crd/bases/blockstor.io.blockstor.io_nodes.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,15 @@ spec:
8484
props is the LINSTOR property map for this node. Keys are LINSTOR
8585
namespace paths (e.g. `Aux/foo`, `DrbdOptions/Net/...`).
8686
type: object
87+
satelliteEndpoint:
88+
description: |-
89+
satelliteEndpoint is the controller→satellite gRPC endpoint
90+
(`host:port`). Phase 10.3: replaces `Props["SatelliteEndpoint"]`
91+
— the dispatcher reads this typed field first, falling back to
92+
the props bag for forward-compat with pre-migration data. The
93+
field becomes irrelevant once Phase 10.6 lands and the gRPC
94+
path is gone.
95+
type: string
8796
type:
8897
description: |-
8998
type is the LINSTOR node role. Common values: SATELLITE, COMBINED,

pkg/dispatcher/dispatcher.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,24 @@ func (d *Dispatcher) Apply(ctx context.Context, target *blockstoriov1alpha1.Reso
141141
return resp.GetResults()[0], nil
142142
}
143143

144-
// lookupEndpoint reads SatelliteEndpoint from the Node prop bag. We
145-
// match by the original LINSTOR name (annotation when slugified, else
146-
// metadata.Name) so non-RFC1123 LINSTOR names still resolve back to
147-
// the right CRD on the controller side.
144+
// lookupEndpoint reads SatelliteEndpoint from the matching Node CRD.
145+
// Phase 10.3: typed `Spec.SatelliteEndpoint` wins; falls back to the
146+
// legacy `Spec.Props["SatelliteEndpoint"]` so a partially-migrated
147+
// cluster (or a satellite that still pushes the prop in its Hello
148+
// handshake) keeps working unchanged. Match by the original LINSTOR
149+
// name (annotation when slugified, else metadata.Name) so
150+
// non-RFC1123 LINSTOR names still resolve back to the right CRD.
148151
func lookupEndpoint(nodeName string, nodes []blockstoriov1alpha1.Node) string {
149152
for i := range nodes {
150-
if k8s.OriginalName(&nodes[i].ObjectMeta) == nodeName {
151-
return nodes[i].Spec.Props["SatelliteEndpoint"]
153+
if k8s.OriginalName(&nodes[i].ObjectMeta) != nodeName {
154+
continue
155+
}
156+
157+
if ep := nodes[i].Spec.SatelliteEndpoint; ep != "" {
158+
return ep
152159
}
160+
161+
return nodes[i].Spec.Props["SatelliteEndpoint"]
153162
}
154163

155164
return ""

pkg/store/k8s/nodes.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package k8s
1818

1919
import (
2020
"context"
21+
"maps"
2122
"sort"
2223
"strings"
2324

@@ -164,11 +165,25 @@ func (n *nodes) Delete(ctx context.Context, name string) error {
164165
}
165166

166167
// crdToWireNode flattens a Node CRD into the LINSTOR REST shape.
168+
// Phase 10.3: re-emits typed `Spec.SatelliteEndpoint` back into the
169+
// wire `Props["SatelliteEndpoint"]` so golinstor + the dispatcher's
170+
// fallback path keep seeing the same shape on GET.
167171
func crdToWireNode(crd *crdv1alpha1.Node) apiv1.Node {
172+
props := crd.Spec.Props
173+
174+
if crd.Spec.SatelliteEndpoint != "" {
175+
props = maps.Clone(props)
176+
if props == nil {
177+
props = map[string]string{}
178+
}
179+
180+
props["SatelliteEndpoint"] = crd.Spec.SatelliteEndpoint
181+
}
182+
168183
out := apiv1.Node{
169184
Name: OriginalName(&crd.ObjectMeta),
170185
Type: crd.Spec.Type,
171-
Props: crd.Spec.Props,
186+
Props: props,
172187
ConnectionStatus: crd.Status.ConnectionStatus,
173188
}
174189

@@ -211,11 +226,18 @@ func wireToCRDNode(in *apiv1.Node) *crdv1alpha1.Node {
211226
// SatelliteEncryptionType is uppercased on the way in: upstream LINSTOR
212227
// accepts mixed case ("plain"/"PLAIN") but the CRD validation enum is
213228
// uppercase-only, so we normalise here to keep wire compatibility.
229+
//
230+
// Phase 10.3: lifts `Props["SatelliteEndpoint"]` into the typed
231+
// `Spec.SatelliteEndpoint` field. The dispatcher reads typed first
232+
// (so this is the source of truth going forward); we still keep the
233+
// original key in Props for forward-compat with any pre-migration
234+
// reader that hasn't been updated.
214235
func wireToCRDNodeSpec(in *apiv1.Node) crdv1alpha1.NodeSpec {
215236
spec := crdv1alpha1.NodeSpec{
216-
Type: strings.ToUpper(in.Type),
217-
Props: in.Props,
218-
Flags: in.Flags,
237+
Type: strings.ToUpper(in.Type),
238+
Props: in.Props,
239+
Flags: in.Flags,
240+
SatelliteEndpoint: in.Props["SatelliteEndpoint"],
219241
}
220242

221243
if len(in.NetInterfaces) > 0 {

0 commit comments

Comments
 (0)