xref: /aosp_15_r20/external/grpc-grpc-java/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java (revision e07d83d3ffcef9ecfc9f7f475418ec639ff0e5fe)
1 /*
2  * Copyright 2019 The gRPC 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 io.grpc.xds;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 
21 import com.google.common.annotations.VisibleForTesting;
22 import com.google.common.base.MoreObjects;
23 import com.google.protobuf.ListValue;
24 import com.google.protobuf.NullValue;
25 import com.google.protobuf.Struct;
26 import com.google.protobuf.Value;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Objects;
32 import javax.annotation.Nullable;
33 
34 /**
35  * Defines gRPC data types for Envoy protobuf messages used in xDS protocol. Each data type has
36  * the same name as Envoy's corresponding protobuf message, but only with fields used by gRPC.
37  *
38  * <p>Each data type should define a {@code fromEnvoyProtoXXX} static method to convert an Envoy
39  * proto message to an instance of that data type.
40  *
41  * <p>For data types that need to be sent as protobuf messages, a {@code toEnvoyProtoXXX} instance
42  * method is defined to convert an instance to Envoy proto message.
43  *
44  * <p>Data conversion should follow the invariant: converted data is guaranteed to be valid for
45  * gRPC. If the protobuf message contains invalid data, the conversion should fail and no object
46  * should be instantiated.
47  */
48 // TODO(chengyuanzhang): put data types into smaller categories.
49 final class EnvoyProtoData {
50 
51   // Prevent instantiation.
EnvoyProtoData()52   private EnvoyProtoData() {
53   }
54 
55   /**
56    * See corresponding Envoy proto message {@link io.envoyproxy.envoy.config.core.v3.Node}.
57    */
58   public static final class Node {
59 
60     private final String id;
61     private final String cluster;
62     @Nullable
63     private final Map<String, ?> metadata;
64     @Nullable
65     private final Locality locality;
66     private final List<Address> listeningAddresses;
67     private final String buildVersion;
68     private final String userAgentName;
69     @Nullable
70     private final String userAgentVersion;
71     private final List<String> clientFeatures;
72 
Node( String id, String cluster, @Nullable Map<String, ?> metadata, @Nullable Locality locality, List<Address> listeningAddresses, String buildVersion, String userAgentName, @Nullable String userAgentVersion, List<String> clientFeatures)73     private Node(
74         String id, String cluster, @Nullable Map<String, ?> metadata, @Nullable Locality locality,
75         List<Address> listeningAddresses, String buildVersion, String userAgentName,
76         @Nullable String userAgentVersion, List<String> clientFeatures) {
77       this.id = checkNotNull(id, "id");
78       this.cluster = checkNotNull(cluster, "cluster");
79       this.metadata = metadata;
80       this.locality = locality;
81       this.listeningAddresses = Collections.unmodifiableList(
82           checkNotNull(listeningAddresses, "listeningAddresses"));
83       this.buildVersion = checkNotNull(buildVersion, "buildVersion");
84       this.userAgentName = checkNotNull(userAgentName, "userAgentName");
85       this.userAgentVersion = userAgentVersion;
86       this.clientFeatures = Collections.unmodifiableList(
87           checkNotNull(clientFeatures, "clientFeatures"));
88     }
89 
90     @Override
toString()91     public String toString() {
92       return MoreObjects.toStringHelper(this)
93           .add("id", id)
94           .add("cluster", cluster)
95           .add("metadata", metadata)
96           .add("locality", locality)
97           .add("listeningAddresses", listeningAddresses)
98           .add("buildVersion", buildVersion)
99           .add("userAgentName", userAgentName)
100           .add("userAgentVersion", userAgentVersion)
101           .add("clientFeatures", clientFeatures)
102           .toString();
103     }
104 
105     @Override
equals(Object o)106     public boolean equals(Object o) {
107       if (this == o) {
108         return true;
109       }
110       if (o == null || getClass() != o.getClass()) {
111         return false;
112       }
113       Node node = (Node) o;
114       return Objects.equals(id, node.id)
115           && Objects.equals(cluster, node.cluster)
116           && Objects.equals(metadata, node.metadata)
117           && Objects.equals(locality, node.locality)
118           && Objects.equals(listeningAddresses, node.listeningAddresses)
119           && Objects.equals(buildVersion, node.buildVersion)
120           && Objects.equals(userAgentName, node.userAgentName)
121           && Objects.equals(userAgentVersion, node.userAgentVersion)
122           && Objects.equals(clientFeatures, node.clientFeatures);
123     }
124 
125     @Override
hashCode()126     public int hashCode() {
127       return Objects
128           .hash(id, cluster, metadata, locality, listeningAddresses, buildVersion, userAgentName,
129               userAgentVersion, clientFeatures);
130     }
131 
132     static final class Builder {
133       private String id = "";
134       private String cluster = "";
135       @Nullable
136       private Map<String, ?> metadata;
137       @Nullable
138       private Locality locality;
139       // TODO(sanjaypujare): eliminate usage of listening_addresses field.
140       private final List<Address> listeningAddresses = new ArrayList<>();
141       private String buildVersion = "";
142       private String userAgentName = "";
143       @Nullable
144       private String userAgentVersion;
145       private final List<String> clientFeatures = new ArrayList<>();
146 
Builder()147       private Builder() {
148       }
149 
setId(String id)150       Builder setId(String id) {
151         this.id = checkNotNull(id, "id");
152         return this;
153       }
154 
setCluster(String cluster)155       Builder setCluster(String cluster) {
156         this.cluster = checkNotNull(cluster, "cluster");
157         return this;
158       }
159 
setMetadata(Map<String, ?> metadata)160       Builder setMetadata(Map<String, ?> metadata) {
161         this.metadata = checkNotNull(metadata, "metadata");
162         return this;
163       }
164 
setLocality(Locality locality)165       Builder setLocality(Locality locality) {
166         this.locality = checkNotNull(locality, "locality");
167         return this;
168       }
169 
addListeningAddresses(Address address)170       Builder addListeningAddresses(Address address) {
171         listeningAddresses.add(checkNotNull(address, "address"));
172         return this;
173       }
174 
setBuildVersion(String buildVersion)175       Builder setBuildVersion(String buildVersion) {
176         this.buildVersion = checkNotNull(buildVersion, "buildVersion");
177         return this;
178       }
179 
setUserAgentName(String userAgentName)180       Builder setUserAgentName(String userAgentName) {
181         this.userAgentName = checkNotNull(userAgentName, "userAgentName");
182         return this;
183       }
184 
setUserAgentVersion(String userAgentVersion)185       Builder setUserAgentVersion(String userAgentVersion) {
186         this.userAgentVersion = checkNotNull(userAgentVersion, "userAgentVersion");
187         return this;
188       }
189 
addClientFeatures(String clientFeature)190       Builder addClientFeatures(String clientFeature) {
191         this.clientFeatures.add(checkNotNull(clientFeature, "clientFeature"));
192         return this;
193       }
194 
build()195       Node build() {
196         return new Node(
197             id, cluster, metadata, locality, listeningAddresses, buildVersion, userAgentName,
198             userAgentVersion, clientFeatures);
199       }
200     }
201 
newBuilder()202     static Builder newBuilder() {
203       return new Builder();
204     }
205 
toBuilder()206     Builder toBuilder() {
207       Builder builder = new Builder();
208       builder.id = id;
209       builder.cluster = cluster;
210       builder.metadata = metadata;
211       builder.locality = locality;
212       builder.buildVersion = buildVersion;
213       builder.listeningAddresses.addAll(listeningAddresses);
214       builder.userAgentName = userAgentName;
215       builder.userAgentVersion = userAgentVersion;
216       builder.clientFeatures.addAll(clientFeatures);
217       return builder;
218     }
219 
getId()220     String getId() {
221       return id;
222     }
223 
getCluster()224     String getCluster() {
225       return cluster;
226     }
227 
228     @Nullable
getMetadata()229     Map<String, ?> getMetadata() {
230       return metadata;
231     }
232 
233     @Nullable
getLocality()234     Locality getLocality() {
235       return locality;
236     }
237 
getListeningAddresses()238     List<Address> getListeningAddresses() {
239       return listeningAddresses;
240     }
241 
242     @SuppressWarnings("deprecation")
243     @VisibleForTesting
toEnvoyProtoNode()244     public io.envoyproxy.envoy.config.core.v3.Node toEnvoyProtoNode() {
245       io.envoyproxy.envoy.config.core.v3.Node.Builder builder =
246           io.envoyproxy.envoy.config.core.v3.Node.newBuilder();
247       builder.setId(id);
248       builder.setCluster(cluster);
249       if (metadata != null) {
250         Struct.Builder structBuilder = Struct.newBuilder();
251         for (Map.Entry<String, ?> entry : metadata.entrySet()) {
252           structBuilder.putFields(entry.getKey(), convertToValue(entry.getValue()));
253         }
254         builder.setMetadata(structBuilder);
255       }
256       if (locality != null) {
257         builder.setLocality(
258             io.envoyproxy.envoy.config.core.v3.Locality.newBuilder()
259                 .setRegion(locality.region())
260                 .setZone(locality.zone())
261                 .setSubZone(locality.subZone()));
262       }
263       for (Address address : listeningAddresses) {
264         builder.addListeningAddresses(address.toEnvoyProtoAddress());
265       }
266       builder.setUserAgentName(userAgentName);
267       if (userAgentVersion != null) {
268         builder.setUserAgentVersion(userAgentVersion);
269       }
270       builder.addAllClientFeatures(clientFeatures);
271       return builder.build();
272     }
273   }
274 
275   /**
276    * Converts Java representation of the given JSON value to protobuf's {@link
277    * com.google.protobuf.Value} representation.
278    *
279    * <p>The given {@code rawObject} must be a valid JSON value in Java representation, which is
280    * either a {@code Map<String, ?>}, {@code List<?>}, {@code String}, {@code Double}, {@code
281    * Boolean}, or {@code null}.
282    */
convertToValue(Object rawObject)283   private static Value convertToValue(Object rawObject) {
284     Value.Builder valueBuilder = Value.newBuilder();
285     if (rawObject == null) {
286       valueBuilder.setNullValue(NullValue.NULL_VALUE);
287     } else if (rawObject instanceof Double) {
288       valueBuilder.setNumberValue((Double) rawObject);
289     } else if (rawObject instanceof String) {
290       valueBuilder.setStringValue((String) rawObject);
291     } else if (rawObject instanceof Boolean) {
292       valueBuilder.setBoolValue((Boolean) rawObject);
293     } else if (rawObject instanceof Map) {
294       Struct.Builder structBuilder = Struct.newBuilder();
295       @SuppressWarnings("unchecked")
296       Map<String, ?> map = (Map<String, ?>) rawObject;
297       for (Map.Entry<String, ?> entry : map.entrySet()) {
298         structBuilder.putFields(entry.getKey(), convertToValue(entry.getValue()));
299       }
300       valueBuilder.setStructValue(structBuilder);
301     } else if (rawObject instanceof List) {
302       ListValue.Builder listBuilder = ListValue.newBuilder();
303       List<?> list = (List<?>) rawObject;
304       for (Object obj : list) {
305         listBuilder.addValues(convertToValue(obj));
306       }
307       valueBuilder.setListValue(listBuilder);
308     }
309     return valueBuilder.build();
310   }
311 
312   /**
313    * See corresponding Envoy proto message {@link io.envoyproxy.envoy.config.core.v3.Address}.
314    */
315   static final class Address {
316     private final String address;
317     private final int port;
318 
Address(String address, int port)319     Address(String address, int port) {
320       this.address = checkNotNull(address, "address");
321       this.port = port;
322     }
323 
toEnvoyProtoAddress()324     io.envoyproxy.envoy.config.core.v3.Address toEnvoyProtoAddress() {
325       return
326           io.envoyproxy.envoy.config.core.v3.Address.newBuilder().setSocketAddress(
327               io.envoyproxy.envoy.config.core.v3.SocketAddress.newBuilder().setAddress(address)
328                   .setPortValue(port)).build();
329     }
330 
toEnvoyProtoAddressV2()331     io.envoyproxy.envoy.api.v2.core.Address toEnvoyProtoAddressV2() {
332       return
333           io.envoyproxy.envoy.api.v2.core.Address.newBuilder().setSocketAddress(
334               io.envoyproxy.envoy.api.v2.core.SocketAddress.newBuilder().setAddress(address)
335                   .setPortValue(port)).build();
336     }
337 
338     @Override
toString()339     public String toString() {
340       return MoreObjects.toStringHelper(this)
341           .add("address", address)
342           .add("port", port)
343           .toString();
344     }
345 
346     @Override
equals(Object o)347     public boolean equals(Object o) {
348       if (this == o) {
349         return true;
350       }
351       if (o == null || getClass() != o.getClass()) {
352         return false;
353       }
354       Address address1 = (Address) o;
355       return port == address1.port && Objects.equals(address, address1.address);
356     }
357 
358     @Override
hashCode()359     public int hashCode() {
360       return Objects.hash(address, port);
361     }
362   }
363 }
364