Skip to content

Commit a3f1f04

Browse files
committed
[backplane] Set Envoy node metadata
1 parent 7375973 commit a3f1f04

File tree

3 files changed

+745
-1
lines changed

3 files changed

+745
-1
lines changed

pkg/backplane/envoy/node.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package envoy
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
8+
discoveryv3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
9+
"google.golang.org/protobuf/types/known/structpb"
10+
)
11+
12+
// NodeMetadata represents metadata attached to an Envoy node.
13+
// This metadata is sent with every XDS discovery request to the control plane.
14+
type NodeMetadata struct {
15+
// Name is the human-readable name of the node/proxy instance
16+
Name string `json:"name,omitempty"`
17+
18+
// PrivateAddress is the private/internal address of the node
19+
// This can be used for internal communication or routing
20+
PrivateAddress string `json:"private_address,omitempty"`
21+
}
22+
23+
// ToMap converts NodeMetadata to a map[string]interface{} for serialization
24+
func (nm *NodeMetadata) ToMap() (map[string]interface{}, error) {
25+
// Marshal to JSON then unmarshal to map to handle all field types properly
26+
data, err := json.Marshal(nm)
27+
if err != nil {
28+
return nil, fmt.Errorf("failed to marshal node metadata: %w", err)
29+
}
30+
31+
var result map[string]interface{}
32+
if err := json.Unmarshal(data, &result); err != nil {
33+
return nil, fmt.Errorf("failed to unmarshal node metadata to map: %w", err)
34+
}
35+
36+
// Remove empty fields
37+
for k, v := range result {
38+
if v == nil || v == "" {
39+
delete(result, k)
40+
}
41+
}
42+
43+
return result, nil
44+
}
45+
46+
// FromMap populates NodeMetadata from data.
47+
func (nm *NodeMetadata) FromMap(data map[string]interface{}) error {
48+
if data == nil {
49+
return nil
50+
}
51+
52+
// Marshal map to JSON then unmarshal to struct
53+
jsonData, err := json.Marshal(data)
54+
if err != nil {
55+
return fmt.Errorf("failed to marshal map data: %w", err)
56+
}
57+
58+
if err := json.Unmarshal(jsonData, nm); err != nil {
59+
return fmt.Errorf("failed to unmarshal data to node metadata: %w", err)
60+
}
61+
62+
return nil
63+
}
64+
65+
// ToStruct converts NodeMetadata to protobuf Struct for use in Envoy Node.
66+
func (nm *NodeMetadata) ToStruct() (*structpb.Struct, error) {
67+
metadataMap, err := nm.ToMap()
68+
if err != nil {
69+
return nil, err
70+
}
71+
72+
if len(metadataMap) == 0 {
73+
return nil, nil
74+
}
75+
76+
return structpb.NewStruct(metadataMap)
77+
}
78+
79+
// FromStruct populates NodeMetadata from a protobuf Struct
80+
func (nm *NodeMetadata) FromStruct(s *structpb.Struct) error {
81+
if s == nil {
82+
return nil
83+
}
84+
85+
return nm.FromMap(s.AsMap())
86+
}
87+
88+
// ExtractFromDiscoveryRequest extracts NodeMetadata from an XDS DiscoveryRequest.
89+
func ExtractFromDiscoveryRequest(req *discoveryv3.DiscoveryRequest) (*NodeMetadata, error) {
90+
if req == nil || req.Node == nil {
91+
return nil, fmt.Errorf("discovery request or node is nil")
92+
}
93+
94+
nm := &NodeMetadata{}
95+
96+
if req.Node.Metadata != nil {
97+
if err := nm.FromStruct(req.Node.Metadata); err != nil {
98+
return nil, fmt.Errorf("failed to extract metadata from discovery request: %w", err)
99+
}
100+
}
101+
102+
return nm, nil
103+
}
104+
105+
// ExtractFromNode extracts NodeMetadata from an Envoy core.Node struct.
106+
func ExtractFromNode(node *corev3.Node) (*NodeMetadata, error) {
107+
if node == nil {
108+
return nil, fmt.Errorf("node is nil")
109+
}
110+
111+
nm := &NodeMetadata{}
112+
113+
if node.Metadata != nil {
114+
if err := nm.FromStruct(node.Metadata); err != nil {
115+
return nil, fmt.Errorf("failed to extract metadata from node: %w", err)
116+
}
117+
}
118+
119+
return nm, nil
120+
}
121+
122+
// String returns a string representation of the NodeMetadata.
123+
func (nm *NodeMetadata) String() string {
124+
data, _ := json.Marshal(nm)
125+
return string(data)
126+
}
127+
128+
// Clone creates a deep copy of the NodeMetadata.
129+
func (nm *NodeMetadata) Clone() *NodeMetadata {
130+
if nm == nil {
131+
return nil
132+
}
133+
134+
return &NodeMetadata{
135+
Name: nm.Name,
136+
PrivateAddress: nm.PrivateAddress,
137+
}
138+
}
139+
140+
// IsEmpty returns true if all fields are empty.
141+
func (nm *NodeMetadata) IsEmpty() bool {
142+
return nm.Name == "" && nm.PrivateAddress == ""
143+
}
144+
145+
// Merge merges another NodeMetadata into this one.
146+
// Non-empty fields from other will overwrite fields in nm.
147+
func (nm *NodeMetadata) Merge(other *NodeMetadata) {
148+
if other == nil {
149+
return
150+
}
151+
152+
if other.Name != "" {
153+
nm.Name = other.Name
154+
}
155+
if other.PrivateAddress != "" {
156+
nm.PrivateAddress = other.PrivateAddress
157+
}
158+
}

0 commit comments

Comments
 (0)