1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.linecorp.centraldogma.xds.k8s.v1;
17
18 import java.util.HashSet;
19 import java.util.LinkedHashMap;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23
24 import org.jspecify.annotations.Nullable;
25
26 import com.google.protobuf.Struct;
27 import com.google.protobuf.Value;
28
29 import com.linecorp.armeria.client.Endpoint;
30 import com.linecorp.armeria.client.kubernetes.endpoints.KubernetesResourceAccess;
31
32 import io.envoyproxy.envoy.config.core.v3.Address;
33 import io.envoyproxy.envoy.config.core.v3.Metadata;
34 import io.envoyproxy.envoy.config.core.v3.SocketAddress;
35 import io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint;
36 import io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints;
37 import io.fabric8.kubernetes.api.model.Node;
38 import io.fabric8.kubernetes.api.model.ObjectMeta;
39 import io.fabric8.kubernetes.api.model.Pod;
40
41
42
43
44
45
46 final class KubernetesEndpointConverter {
47
48 private static final String DEFAULT_METADATA_NAMESPACE = "envoy.lb";
49
50
51
52
53
54
55 static void addLbEndpoints(LocalityLbEndpoints.Builder builder, Iterable<Endpoint> endpoints,
56 ServiceEndpointWatcher watcher) {
57 final boolean distinct = watcher.getDistinctEndpoint();
58 final List<MetadataMapping> mappings = watcher.getMetadataMappingList();
59 final Set<String> seen = distinct ? new HashSet<>() : null;
60 for (Endpoint endpoint : endpoints) {
61 if (!endpoint.hasPort()) {
62 continue;
63 }
64 if (seen != null && !seen.add(endpoint.host() + ':' + endpoint.port())) {
65 continue;
66 }
67 final SocketAddress socketAddress = SocketAddress.newBuilder()
68 .setAddress(endpoint.host())
69 .setPortValue(endpoint.port())
70 .build();
71 final LbEndpoint.Builder lbEndpointBuilder =
72 LbEndpoint.newBuilder()
73 .setEndpoint(io.envoyproxy.envoy.config.endpoint.v3.Endpoint.newBuilder()
74 .setAddress(Address.newBuilder()
75 .setSocketAddress(socketAddress)
76 .build())
77 .build());
78 final Metadata metadata = buildMetadata(endpoint, mappings);
79 if (metadata != null) {
80 lbEndpointBuilder.setMetadata(metadata);
81 }
82 builder.addLbEndpoints(lbEndpointBuilder.build());
83 }
84 }
85
86 @Nullable
87 private static Metadata buildMetadata(Endpoint endpoint, List<MetadataMapping> mappings) {
88 if (mappings.isEmpty()) {
89 return null;
90 }
91 final Map<String, Struct.Builder> structsByNamespace = new LinkedHashMap<>();
92 for (MetadataMapping mapping : mappings) {
93 final ObjectMeta objectMeta = objectMeta(endpoint, mapping.getResourceType());
94 if (objectMeta == null) {
95 continue;
96 }
97 final Map<String, String> source =
98 mapping.getEntryType() == MetadataMapping.EntryType.ANNOTATION ? objectMeta.getAnnotations()
99 : objectMeta.getLabels();
100 if (source == null || source.isEmpty()) {
101 continue;
102 }
103 final String namespace = mapping.getMetadataNamespace().isEmpty() ? DEFAULT_METADATA_NAMESPACE
104 : mapping.getMetadataNamespace();
105 switch (mapping.getSourceCase()) {
106 case SOURCE_KEY:
107
108 final String value = source.get(mapping.getSourceKey());
109 if (value != null) {
110 final String key = mapping.getMetadataKey().isEmpty() ? mapping.getSourceKey()
111 : mapping.getMetadataKey();
112 putField(structsByNamespace, namespace, key, value);
113 }
114 break;
115 case SOURCE_KEY_PREFIX:
116
117 final String prefix = mapping.getSourceKeyPrefix();
118 for (Map.Entry<String, String> entry : source.entrySet()) {
119 if (entry.getKey().startsWith(prefix)) {
120 putField(structsByNamespace, namespace, entry.getKey(), entry.getValue());
121 }
122 }
123 break;
124 default:
125
126 break;
127 }
128 }
129 if (structsByNamespace.isEmpty()) {
130 return null;
131 }
132 final Metadata.Builder metadataBuilder = Metadata.newBuilder();
133 structsByNamespace.forEach((namespace, struct) ->
134 metadataBuilder.putFilterMetadata(namespace, struct.build()));
135 return metadataBuilder.build();
136 }
137
138 private static void putField(Map<String, Struct.Builder> structsByNamespace, String namespace,
139 String key, String value) {
140 structsByNamespace.computeIfAbsent(namespace, unused -> Struct.newBuilder())
141 .putFields(key, Value.newBuilder().setStringValue(value).build());
142 }
143
144 @Nullable
145 private static ObjectMeta objectMeta(Endpoint endpoint, MetadataMapping.ResourceType resourceType) {
146 switch (resourceType) {
147 case POD:
148 final Pod pod = KubernetesResourceAccess.pod(endpoint);
149 return pod != null ? pod.getMetadata() : null;
150 case NODE:
151 final Node node = KubernetesResourceAccess.node(endpoint);
152 return node != null ? node.getMetadata() : null;
153 default:
154 return null;
155 }
156 }
157
158 private KubernetesEndpointConverter() {}
159 }