• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #19592

17 Dec 2024 12:03AM UTC coverage: 88.586% (+0.01%) from 88.573%
#19592

push

github

ejona86
xds: Move specialized APIs out of XdsResourceType

StructOrError is a more generic API, but we have StatusOr now so we
don't want new usages of StructOrError. Moving StructOrError out of
io.grpc.xds.client will make it easier to delete StructOrError once
we've migrated to StatusOr in the future.

TRANSPORT_SOCKET_NAME_TLS should also move, but it wasn't immediately
clear to me where it should go.

33474 of 37787 relevant lines covered (88.59%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.1
/../xds/src/main/java/io/grpc/xds/client/XdsResourceType.java
1
/*
2
 * Copyright 2022 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.client;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20
import static io.grpc.xds.client.XdsClient.canonifyResourceName;
21
import static io.grpc.xds.client.XdsClient.isResourceNameValid;
22

23
import com.google.protobuf.Any;
24
import com.google.protobuf.InvalidProtocolBufferException;
25
import com.google.protobuf.Message;
26
import io.envoyproxy.envoy.service.discovery.v3.Resource;
27
import io.grpc.ExperimentalApi;
28
import io.grpc.xds.client.Bootstrapper.ServerInfo;
29
import io.grpc.xds.client.XdsClient.ResourceUpdate;
30
import java.util.ArrayList;
31
import java.util.HashMap;
32
import java.util.HashSet;
33
import java.util.List;
34
import java.util.Map;
35
import java.util.Set;
36
import javax.annotation.Nullable;
37

38
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/10847")
39
public abstract class XdsResourceType<T extends ResourceUpdate> {
1✔
40
  static final String TYPE_URL_RESOURCE =
41
      "type.googleapis.com/envoy.service.discovery.v3.Resource";
42
  protected static final String TRANSPORT_SOCKET_NAME_TLS = "envoy.transport_sockets.tls";
43

44
  protected static final String TYPE_URL_TYPED_STRUCT_UDPA =
45
      "type.googleapis.com/udpa.type.v1.TypedStruct";
46
  protected static final String TYPE_URL_TYPED_STRUCT =
47
      "type.googleapis.com/xds.type.v3.TypedStruct";
48

49
  /**
50
   * Extract the resource name from an older resource type that included the name within the
51
   * resource contents itself. The newer approach has resources wrapped with {@code
52
   * envoy.service.discovery.v3.Resource} which then provides the name. This method is only called
53
   * for the old approach.
54
   *
55
   * @return the resource's name, or {@code null} if name is not stored within the resource contents
56
   */
57
  @Nullable
58
  protected String extractResourceName(Message unpackedResource) {
59
    return null;
×
60
  }
61

62
  protected abstract Class<? extends com.google.protobuf.Message> unpackedClassName();
63

64
  public abstract String typeName();
65

66
  public abstract String typeUrl();
67

68
  public abstract boolean shouldRetrieveResourceKeysForArgs();
69

70
  // Do not confuse with the SotW approach: it is the mechanism in which the client must specify all
71
  // resource names it is interested in with each request. Different resource types may behave
72
  // differently in this approach. For LDS and CDS resources, the server must return all resources
73
  // that the client has subscribed to in each request. For RDS and EDS, the server may only return
74
  // the resources that need an update.
75
  protected abstract boolean isFullStateOfTheWorld();
76

77
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/10847")
78
  public static class Args {
79
    final ServerInfo serverInfo;
80
    final String versionInfo;
81
    final String nonce;
82
    final Bootstrapper.BootstrapInfo bootstrapInfo;
83
    final Object securityConfig;
84
    // Management server is required to always send newly requested resources, even if they
85
    // may have been sent previously (proactively). Thus, client does not need to cache
86
    // unrequested resources.
87
    // Only resources in the set needs to be parsed. Null means parse everything.
88
    final @Nullable Set<String> subscribedResources;
89

90
    public Args(ServerInfo serverInfo, String versionInfo, String nonce,
91
                Bootstrapper.BootstrapInfo bootstrapInfo,
92
                Object securityConfig,
93
                @Nullable Set<String> subscribedResources) {
1✔
94
      this.serverInfo = serverInfo;
1✔
95
      this.versionInfo = versionInfo;
1✔
96
      this.nonce = nonce;
1✔
97
      this.bootstrapInfo = bootstrapInfo;
1✔
98
      this.securityConfig = securityConfig;
1✔
99
      this.subscribedResources = subscribedResources;
1✔
100
    }
1✔
101

102
    public ServerInfo getServerInfo() {
103
      return serverInfo;
1✔
104
    }
105

106
    public String getNonce() {
107
      return nonce;
×
108
    }
109

110
    public Bootstrapper.BootstrapInfo getBootstrapInfo() {
111
      return bootstrapInfo;
1✔
112
    }
113

114
    public Object getSecurityConfig() {
115
      return securityConfig;
1✔
116
    }
117
  }
118

119
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/10847")
120
  public static final class ResourceInvalidException extends Exception {
121
    private static final long serialVersionUID = 0L;
122

123
    public ResourceInvalidException(String message) {
124
      super(message, null, false, false);
1✔
125
    }
1✔
126

127
    public ResourceInvalidException(String message, Throwable cause) {
128
      super(cause != null ? message + ": " + cause.getMessage() : message, cause, false, false);
1✔
129
    }
1✔
130
  }
131

132
  ValidatedResourceUpdate<T> parse(Args args, List<Any> resources) {
133
    Map<String, ParsedResource<T>> parsedResources = new HashMap<>(resources.size());
1✔
134
    Set<String> unpackedResources = new HashSet<>(resources.size());
1✔
135
    Set<String> invalidResources = new HashSet<>();
1✔
136
    List<String> errors = new ArrayList<>();
1✔
137

138
    for (int i = 0; i < resources.size(); i++) {
1✔
139
      Any resource = resources.get(i);
1✔
140

141
      Message unpackedMessage;
142
      String name = "";
1✔
143
      try {
144
        if (resource.getTypeUrl().equals(TYPE_URL_RESOURCE)) {
1✔
145
          Resource wrappedResource = unpackCompatibleType(resource, Resource.class,
1✔
146
              TYPE_URL_RESOURCE, null);
147
          resource = wrappedResource.getResource();
1✔
148
          name = wrappedResource.getName();
1✔
149
        } 
150
        unpackedMessage = unpackCompatibleType(resource, unpackedClassName(), typeUrl(), null);
1✔
151
      } catch (InvalidProtocolBufferException e) {
1✔
152
        errors.add(String.format("%s response Resource index %d - can't decode %s: %s",
1✔
153
                typeName(), i, unpackedClassName().getSimpleName(), e.getMessage()));
1✔
154
        continue;
1✔
155
      }
1✔
156
      // Fallback to inner resource name if the outer resource didn't have a name.
157
      if (name.isEmpty()) {
1✔
158
        name = extractResourceName(unpackedMessage);
1✔
159
      }
160
      if (name == null || !isResourceNameValid(name, resource.getTypeUrl())) {
1✔
161
        errors.add(
1✔
162
            "Unsupported resource name: " + name + " for type: " + typeName());
1✔
163
        continue;
1✔
164
      }
165
      String cname = canonifyResourceName(name);
1✔
166
      if (args.subscribedResources != null && !args.subscribedResources.contains(name)) {
1✔
167
        continue;
1✔
168
      }
169
      unpackedResources.add(cname);
1✔
170

171
      T resourceUpdate;
172
      try {
173
        resourceUpdate = doParse(args, unpackedMessage);
1✔
174
      } catch (ResourceInvalidException e) {
1✔
175
        errors.add(String.format("%s response %s '%s' validation error: %s",
1✔
176
                typeName(), unpackedClassName().getSimpleName(), cname, e.getMessage()));
1✔
177
        invalidResources.add(cname);
1✔
178
        continue;
1✔
179
      }
1✔
180

181
      // Resource parsed successfully.
182
      parsedResources.put(cname, new ParsedResource<T>(resourceUpdate, resource));
1✔
183
    }
184
    return new ValidatedResourceUpdate<T>(parsedResources, unpackedResources, invalidResources,
1✔
185
        errors);
186

187
  }
188

189
  protected abstract T doParse(Args args, Message unpackedMessage) throws ResourceInvalidException;
190

191
  /**
192
   * Helper method to unpack serialized {@link com.google.protobuf.Any} message, while replacing
193
   * Type URL {@code compatibleTypeUrl} with {@code typeUrl}.
194
   *
195
   * @param <T> The type of unpacked message
196
   * @param any serialized message to unpack
197
   * @param clazz the class to unpack the message to
198
   * @param typeUrl type URL to replace message Type URL, when it's compatible
199
   * @param compatibleTypeUrl compatible Type URL to be replaced with {@code typeUrl}
200
   * @return Unpacked message
201
   * @throws InvalidProtocolBufferException if the message couldn't be unpacked
202
   */
203
  protected static <T extends com.google.protobuf.Message> T unpackCompatibleType(
204
      Any any, Class<T> clazz, String typeUrl, String compatibleTypeUrl)
205
      throws InvalidProtocolBufferException {
206
    if (any.getTypeUrl().equals(compatibleTypeUrl)) {
1✔
207
      any = any.toBuilder().setTypeUrl(typeUrl).build();
1✔
208
    }
209
    return any.unpack(clazz);
1✔
210
  }
211

212
  static final class ParsedResource<T extends ResourceUpdate> {
213
    private final T resourceUpdate;
214
    private final Any rawResource;
215

216
    public ParsedResource(T resourceUpdate, Any rawResource) {
1✔
217
      this.resourceUpdate = checkNotNull(resourceUpdate, "resourceUpdate");
1✔
218
      this.rawResource = checkNotNull(rawResource, "rawResource");
1✔
219
    }
1✔
220

221
    T getResourceUpdate() {
222
      return resourceUpdate;
1✔
223
    }
224

225
    Any getRawResource() {
226
      return rawResource;
1✔
227
    }
228
  }
229

230
  static final class ValidatedResourceUpdate<T extends ResourceUpdate> {
231
    Map<String, ParsedResource<T>> parsedResources;
232
    Set<String> unpackedResources;
233
    Set<String> invalidResources;
234
    List<String> errors;
235

236
    // validated resource update
237
    public ValidatedResourceUpdate(Map<String, ParsedResource<T>> parsedResources,
238
                                   Set<String> unpackedResources,
239
                                   Set<String> invalidResources,
240
                                   List<String> errors) {
1✔
241
      this.parsedResources = parsedResources;
1✔
242
      this.unpackedResources = unpackedResources;
1✔
243
      this.invalidResources = invalidResources;
1✔
244
      this.errors = errors;
1✔
245
    }
1✔
246
  }
247
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc