Skip to content

Commit 2d2290a

Browse files
committed
add xds metadata registry
add copyright remove trailing whitespaces fix linter correct imports use netip add comments fix linter keep fixing add back test check linter move out of xdsmetadataregistry fix linter
1 parent 3c743c9 commit 2d2290a

5 files changed

Lines changed: 400 additions & 0 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
*
3+
* Copyright 2025 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package xdsresource
19+
20+
import (
21+
"encoding/json"
22+
"fmt"
23+
"net/netip"
24+
25+
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
26+
"google.golang.org/protobuf/proto"
27+
)
28+
29+
func init() {
30+
Register("envoy.http11_proxy_transport_socket.proxy_address", ProxyAddressConvertor{})
31+
}
32+
33+
var (
34+
// metadata_registry is a map from proto type to Converter.
35+
metadataregistry = make(map[string]Converter)
36+
)
37+
38+
// MetadataValue is the interface for a converted metadata value. It is
39+
// implemented by concrete types that hold the converted metadata.
40+
type MetadataValue interface {
41+
Type() string
42+
}
43+
44+
// Converter is the interface for a metadata converter. It is implemented by
45+
// concrete types that convert raw bytes into a MetadataValue.
46+
type Converter interface {
47+
// Convert parses the raw bytes of an Any proto into a MetadataValue.
48+
Convert([]byte) (MetadataValue, error)
49+
}
50+
51+
// Register registers the converter to the map keyed on a proto type. Must be
52+
// called at init time. Not thread safe.
53+
func Register(protoType string, c Converter) {
54+
metadataregistry[protoType] = c
55+
}
56+
57+
// ConverterForType retrieves a converter based on key given.
58+
func ConverterForType(typeURL string) Converter {
59+
return metadataregistry[typeURL]
60+
}
61+
62+
// JSONMetadata stores the values in a google.protobufStruct from
63+
// FilterMetadata.
64+
type JSONMetadata struct {
65+
MetadataValue
66+
Data json.RawMessage
67+
}
68+
69+
// ProxyAddressMetadataValue holds the address parsed from A.86 metadata.
70+
type ProxyAddressMetadataValue struct {
71+
MetadataValue
72+
Address string
73+
}
74+
75+
// Type returns a string representing this metadata type.
76+
func (ProxyAddressMetadataValue) Type() string {
77+
return "envoy.config.core.v3.Address"
78+
}
79+
80+
// ProxyAddressConvertor implements the converter for A86 (Proxy Address) metadata.
81+
type ProxyAddressConvertor struct{}
82+
83+
// Convert parses the raw bytes of an Any proto containing an Address proto into
84+
// a ProxyAddressMetadataValue.
85+
func (ProxyAddressConvertor) Convert(anyBytes []byte) (MetadataValue, error) {
86+
addressProto := &v3corepb.Address{}
87+
if err := proto.Unmarshal(anyBytes, addressProto); err != nil {
88+
return nil, fmt.Errorf("failed to unmarshal resource: %v", err)
89+
}
90+
socketaddress := addressProto.GetSocketAddress()
91+
if socketaddress == nil {
92+
return nil, fmt.Errorf("no socket_address field in metadata")
93+
}
94+
portvalue := socketaddress.GetPortValue()
95+
if portvalue == 0 {
96+
return nil, fmt.Errorf("port value not set in socket_address")
97+
}
98+
if _, err := netip.ParseAddr(socketaddress.GetAddress()); err != nil {
99+
return nil, fmt.Errorf("address field is not a valid IPv4 or IPv6 address: %q", socketaddress.GetAddress())
100+
}
101+
metadata := ProxyAddressMetadataValue{
102+
Address: socketaddress.Address,
103+
}
104+
return metadata, nil
105+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
*
3+
* Copyright 2025 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package xdsresource
18+
19+
import (
20+
"testing"
21+
22+
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
23+
"github.com/google/go-cmp/cmp"
24+
"google.golang.org/protobuf/proto"
25+
)
26+
27+
const proxyAddressFilterName = "envoy.http11_proxy_transport_socket.proxy_address"
28+
29+
func (s) TestProxyAddressConverterSuccess(t *testing.T) {
30+
converter := ConverterForType(proxyAddressFilterName)
31+
if converter == nil {
32+
t.Fatalf("Converter for %q not found in registry", proxyAddressFilterName)
33+
}
34+
tests := []struct {
35+
name string
36+
addr *v3corepb.Address
37+
want ProxyAddressMetadataValue
38+
}{
39+
{
40+
name: "valid IPv4 address and port",
41+
addr: &v3corepb.Address{
42+
Address: &v3corepb.Address_SocketAddress{
43+
SocketAddress: &v3corepb.SocketAddress{
44+
Address: "192.168.1.1",
45+
PortSpecifier: &v3corepb.SocketAddress_PortValue{
46+
PortValue: 8080,
47+
},
48+
},
49+
},
50+
},
51+
want: ProxyAddressMetadataValue{
52+
Address: "192.168.1.1",
53+
},
54+
},
55+
{
56+
name: "valid IPv6 address and port",
57+
addr: &v3corepb.Address{
58+
Address: &v3corepb.Address_SocketAddress{
59+
SocketAddress: &v3corepb.SocketAddress{
60+
Address: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
61+
PortSpecifier: &v3corepb.SocketAddress_PortValue{
62+
PortValue: 9090,
63+
},
64+
},
65+
},
66+
},
67+
want: ProxyAddressMetadataValue{
68+
Address: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
69+
},
70+
},
71+
}
72+
73+
for _, tt := range tests {
74+
t.Run(tt.name, func(t *testing.T) {
75+
anyBytes, err := proto.Marshal(tt.addr)
76+
if err != nil {
77+
t.Fatalf("Failed to marshal address proto: %v", err)
78+
}
79+
got, err := converter.Convert(anyBytes)
80+
if err != nil {
81+
t.Fatalf("convert() failed with error: %v", err)
82+
}
83+
if diff := cmp.Diff(tt.want, got, cmp.AllowUnexported(ProxyAddressMetadataValue{})); diff != "" {
84+
t.Errorf("convert() returned unexpected value:\n%s", diff)
85+
}
86+
})
87+
}
88+
}
89+
90+
func (s) TestProxyAddressConverterFailure(t *testing.T) {
91+
converter := ConverterForType(proxyAddressFilterName)
92+
if converter == nil {
93+
t.Fatalf("Converter for %q not found in registry", proxyAddressFilterName)
94+
}
95+
tests := []struct {
96+
name string
97+
addr *v3corepb.Address
98+
wantErr string
99+
}{
100+
{
101+
name: "invalid address",
102+
addr: &v3corepb.Address{
103+
Address: &v3corepb.Address_SocketAddress{
104+
SocketAddress: &v3corepb.SocketAddress{
105+
Address: "invalid-ip",
106+
PortSpecifier: &v3corepb.SocketAddress_PortValue{
107+
PortValue: 8080,
108+
},
109+
},
110+
},
111+
},
112+
wantErr: "address field is not a valid IPv4 or IPv6 address: \"invalid-ip\"",
113+
},
114+
{
115+
name: "missing socket_address",
116+
addr: &v3corepb.Address{
117+
// No SocketAddress field set.
118+
},
119+
wantErr: "no socket_address field in metadata",
120+
},
121+
{
122+
name: "address is not a socket address",
123+
addr: &v3corepb.Address{
124+
Address: &v3corepb.Address_EnvoyInternalAddress{
125+
EnvoyInternalAddress: &v3corepb.EnvoyInternalAddress{
126+
AddressNameSpecifier: &v3corepb.EnvoyInternalAddress_ServerListenerName{
127+
ServerListenerName: "some-internal-listener",
128+
},
129+
},
130+
},
131+
},
132+
wantErr: "no socket_address field in metadata",
133+
},
134+
{
135+
name: "port value not set",
136+
addr: &v3corepb.Address{
137+
Address: &v3corepb.Address_SocketAddress{
138+
SocketAddress: &v3corepb.SocketAddress{
139+
Address: "127.0.0.1",
140+
PortSpecifier: nil,
141+
},
142+
},
143+
},
144+
wantErr: "port value not set in socket_address",
145+
},
146+
}
147+
148+
for _, tt := range tests {
149+
t.Run(tt.name, func(t *testing.T) {
150+
anyBytes, err := proto.Marshal(tt.addr)
151+
if err != nil {
152+
t.Fatalf("Failed to marshal address proto: %v", err)
153+
}
154+
155+
// Call the convert function and check the returned error.
156+
_, gotErr := converter.Convert(anyBytes)
157+
if gotErr == nil || gotErr.Error() != tt.wantErr {
158+
t.Errorf("convert() got error = %v, wantErr = %q", gotErr, tt.wantErr)
159+
}
160+
})
161+
}
162+
}

internal/xds/xdsclient/xdsresource/type_eds.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,9 @@ type EndpointsUpdate struct {
7474
// Raw is the resource from the xds response.
7575
Raw *anypb.Any
7676
}
77+
78+
// Metadata stores a map from keys to internal representations of metadata
79+
// proto.
80+
type Metadata struct {
81+
Metadata map[string]MetadataValue
82+
}

internal/xds/xdsclient/xdsresource/unmarshal_eds.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package xdsresource
1919

2020
import (
21+
"encoding/json"
2122
"fmt"
2223
"math"
2324
"net"
@@ -30,6 +31,7 @@ import (
3031
"google.golang.org/grpc/internal/pretty"
3132
xdsinternal "google.golang.org/grpc/internal/xds"
3233
"google.golang.org/grpc/internal/xds/clients"
34+
"google.golang.org/protobuf/encoding/protojson"
3335
"google.golang.org/protobuf/proto"
3436
"google.golang.org/protobuf/types/known/anypb"
3537
)
@@ -204,3 +206,40 @@ func parseEDSRespProto(m *v3endpointpb.ClusterLoadAssignment) (EndpointsUpdate,
204206
}
205207
return ret, nil
206208
}
209+
210+
func validateAndConstructMetadata(metadataProto *v3corepb.Metadata) (Metadata, error) {
211+
metadata := make(map[string]MetadataValue)
212+
if metadataProto == nil {
213+
return Metadata{Metadata: metadata}, nil
214+
}
215+
// First go through TypedFilterMetadata.
216+
for key, anyProto := range metadataProto.GetTypedFilterMetadata() {
217+
converter := ConverterForType(anyProto.GetTypeUrl())
218+
// Ignore types we don't have a converter for.
219+
if converter == nil {
220+
continue
221+
}
222+
223+
val, err := converter.Convert(anyProto.GetValue())
224+
if err != nil {
225+
// If the converter fails, nack the whole resource.
226+
return Metadata{}, fmt.Errorf("metadata parser for key %q and type %q failed: %v", key, anyProto.GetTypeUrl(), err)
227+
}
228+
metadata[key] = val
229+
}
230+
231+
// Process FilterMetadata for any keys not already handled.
232+
for key, structProto := range metadataProto.GetFilterMetadata() {
233+
_, exists := metadata[key]
234+
// Skip keys already added from TyperFilterMetadata.
235+
if exists {
236+
continue
237+
}
238+
b, err := protojson.Marshal(structProto)
239+
if err != nil {
240+
return Metadata{}, fmt.Errorf("failed to marshal filter metadata for key %q: %v", key, err)
241+
}
242+
metadata[key] = JSONMetadata{Data: json.RawMessage(b)}
243+
}
244+
return Metadata{Metadata: metadata}, nil
245+
}

0 commit comments

Comments
 (0)