1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificate;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ContainerEndpoint;
import com.yahoo.vespa.hosted.controller.application.AssignedRotation;
import com.yahoo.vespa.hosted.controller.application.Endpoint;
import com.yahoo.vespa.hosted.controller.application.EndpointId;
import com.yahoo.vespa.hosted.controller.application.EndpointList;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* This represents the endpoints, and associated resources, that have been prepared for a deployment.
*
* @author mpolden
*/
public record PreparedEndpoints(DeploymentId deployment,
EndpointList endpoints,
List<AssignedRotation> rotations,
EndpointCertificate certificate) {
public PreparedEndpoints(DeploymentId deployment, EndpointList endpoints, List<AssignedRotation> rotations, EndpointCertificate certificate) {
this.deployment = Objects.requireNonNull(deployment);
this.endpoints = Objects.requireNonNull(endpoints);
this.rotations = List.copyOf(Objects.requireNonNull(rotations));
this.certificate = requireMatchingSans(certificate, endpoints);
}
/** Returns the endpoints contained in this as {@link com.yahoo.vespa.hosted.controller.api.integration.configserver.ContainerEndpoint} */
public Set<ContainerEndpoint> containerEndpoints() {
Map<EndpointId, AssignedRotation> rotationsByEndpointId = rotations.stream()
.collect(Collectors.toMap(AssignedRotation::endpointId,
Function.identity()));
Set<ContainerEndpoint> containerEndpoints = new HashSet<>();
endpoints.scope(Endpoint.Scope.zone).groupingBy(Endpoint::cluster).forEach((clusterId, clusterEndpoints) -> {
clusterEndpoints.groupingBy(Endpoint::authMethod).forEach((authMethod, endpointsByAuthMethod) -> {
containerEndpoints.add(new ContainerEndpoint(clusterId.value(),
asString(Endpoint.Scope.zone),
endpointsByAuthMethod.mapToList(Endpoint::dnsName),
OptionalInt.empty(),
endpointsByAuthMethod.first().get().routingMethod(),
authMethod));
});
});
endpoints.scope(Endpoint.Scope.global).groupingBy(Endpoint::cluster).forEach((clusterId, clusterEndpoints) -> {
for (var endpoint : clusterEndpoints) {
List<String> names = new ArrayList<>(2);
names.add(endpoint.dnsName());
if (endpoint.requiresRotation()) {
EndpointId endpointId = EndpointId.of(endpoint.name());
AssignedRotation rotation = rotationsByEndpointId.get(endpointId);
if (rotation == null) {
throw new IllegalStateException(endpoint + " requires a rotation, but no rotation has been assigned to " + endpointId);
}
// Include the rotation ID as a valid name of this container endpoint
// (required by global routing health checks)
names.add(rotation.rotationId().asString());
}
containerEndpoints.add(new ContainerEndpoint(clusterId.value(),
asString(Endpoint.Scope.global),
names,
OptionalInt.empty(),
endpoint.routingMethod(),
endpoint.authMethod()));
}
});
endpoints.scope(Endpoint.Scope.application).groupingBy(Endpoint::cluster).forEach((clusterId, clusterEndpoints) -> {
for (var endpoint : clusterEndpoints) {
Optional<Endpoint.Target> matchingTarget = endpoint.targets().stream()
.filter(t -> t.routesTo(deployment))
.findFirst();
if (matchingTarget.isEmpty()) throw new IllegalStateException("No target found routing to " + deployment + " in " + endpoint);
containerEndpoints.add(new ContainerEndpoint(clusterId.value(),
asString(Endpoint.Scope.application),
List.of(endpoint.dnsName()),
OptionalInt.of(matchingTarget.get().weight()),
endpoint.routingMethod(),
endpoint.authMethod()));
}
});
return containerEndpoints;
}
private static String asString(Endpoint.Scope scope) {
return switch (scope) {
case application -> "application";
case global -> "global";
case weighted -> "weighted";
case zone -> "zone";
};
}
private static EndpointCertificate requireMatchingSans(EndpointCertificate certificate, EndpointList endpoints) {
Objects.requireNonNull(certificate);
for (var endpoint : endpoints.not().scope(Endpoint.Scope.weighted)) { // Weighted endpoints are not present in certificate
if (!certificate.sanMatches(endpoint.dnsName())) {
throw new IllegalArgumentException(endpoint + " has no matching SAN. Certificate contains " +
certificate.requestedDnsSans());
}
}
return certificate;
}
}
|