summaryrefslogtreecommitdiffstats
path: root/config-provisioning
diff options
context:
space:
mode:
authorjonmv <venstad@gmail.com>2023-01-27 09:17:28 +0100
committerjonmv <venstad@gmail.com>2023-01-27 09:17:28 +0100
commit78a42e1cf9735f58e7f204f34e0cb2bd7c1a3674 (patch)
treeb6c5f1b15ad3729fd8cf10a5e8dd10b2e72d9f0e /config-provisioning
parentb44b2ce5fd913f56d781dba9efe7cb6006584a7d (diff)
Refactor endpoint verification during deployments
Diffstat (limited to 'config-provisioning')
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/EndpointsChecker.java114
1 files changed, 114 insertions, 0 deletions
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/EndpointsChecker.java b/config-provisioning/src/main/java/com/yahoo/config/provision/EndpointsChecker.java
new file mode 100644
index 00000000000..d05e26cf5a5
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/EndpointsChecker.java
@@ -0,0 +1,114 @@
+package com.yahoo.config.provision;
+
+import ai.vespa.http.DomainName;
+
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.InitialDirContext;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author jonmv
+ */
+public class EndpointsChecker {
+
+ public record Endpoint(ClusterSpec.Id clusterName,
+ DomainName dnsName,
+ Optional<InetAddress> ipAddress,
+ Optional<DomainName> canonicalName,
+ boolean isPublic) { }
+
+ public record UnavailabilityCause(String message) { }
+
+ public interface HostNameResolver { Optional<InetAddress> resolve(DomainName hostName); }
+
+ public interface CNameResolver { Optional<DomainName> resolve(DomainName hostName); }
+
+ private EndpointsChecker() { }
+
+ public static Optional<UnavailabilityCause> endpointsAvailable(List<Endpoint> zoneEndpoints) {
+ return endpointsAvailable(zoneEndpoints, EndpointsChecker::resolveHostName, EndpointsChecker::resolveCname);
+ }
+
+ public static Optional<UnavailabilityCause> endpointsAvailable(List<Endpoint> zoneEndpoints,
+ HostNameResolver hostNameResolver,
+ CNameResolver cNameResolver) {
+ if (zoneEndpoints.isEmpty())
+ return Optional.of(new UnavailabilityCause("Endpoints not yet ready."));
+
+ for (Endpoint endpoint : zoneEndpoints) {
+ Optional<InetAddress> resolvedIpAddress = hostNameResolver.resolve(endpoint.dnsName());
+ if (resolvedIpAddress.isEmpty())
+ return Optional.of(new UnavailabilityCause("DNS lookup yielded no IP address for '" + endpoint.dnsName() + "'."));
+
+ if (resolvedIpAddress.equals(endpoint.ipAddress())) // We expect a certain IP address, and that's what we got, so we're good.
+ continue;
+
+ if (endpoint.ipAddress().isPresent()) // We expect a certain IP address, but that's not what we got.
+ return Optional.of(new UnavailabilityCause("IP address of '" + endpoint.dnsName() + "' (" +
+ resolvedIpAddress.get().getHostAddress() + ") and load balancer " +
+ "' (" + endpoint.ipAddress().get().getHostAddress() + ") are not equal"));
+
+ if (endpoint.canonicalName().isEmpty()) // We have no expected IP address, and no canonical name, so there's nothing more to check.
+ continue;
+
+ Optional<DomainName> cNameValue = cNameResolver.resolve(endpoint.dnsName());
+ if (cNameValue.filter(endpoint.canonicalName().get()::equals).isEmpty()) {
+ return Optional.of(new UnavailabilityCause("CNAME '" + endpoint.dnsName() + "' points at " +
+ cNameValue.map(name -> "'" + name + "'").orElse("nothing") +
+ " but should point at load balancer " +
+ endpoint.canonicalName().map(name -> "'" + name + "'").orElse("nothing")));
+ }
+
+ Optional<InetAddress> loadBalancerAddress = hostNameResolver.resolve(endpoint.canonicalName().get());
+ if ( ! loadBalancerAddress.equals(resolvedIpAddress)) {
+ return Optional.of(new UnavailabilityCause("IP address of CNAME '" + endpoint.dnsName() + "' (" +
+ resolvedIpAddress.get().getHostAddress() + ") and load balancer '" +
+ endpoint.canonicalName().get() + "' (" +
+ loadBalancerAddress.map(InetAddress::getHostAddress).orElse("empty") + ") are not equal"));
+ }
+ }
+ return Optional.empty();
+ }
+
+ /** Returns the IP address of the given host name, if any. */
+ static Optional<InetAddress> resolveHostName(DomainName hostname) {
+ try {
+ return Optional.of(InetAddress.getByName(hostname.value()));
+ }
+ catch (UnknownHostException ignored) {
+ return Optional.empty();
+ }
+ }
+
+ /** Returns the host name of the given CNAME, if any. */
+ static Optional<DomainName> resolveCname(DomainName endpoint) {
+ try {
+ InitialDirContext ctx = new InitialDirContext();
+ try {
+ Attributes attrs = ctx.getAttributes("dns:/" + endpoint.value(), new String[]{ "CNAME" });
+ for (Attribute attribute : Collections.list(attrs.getAll())) {
+ Enumeration<?> vals = attribute.getAll();
+ if (vals.hasMoreElements()) {
+ String hostname = vals.nextElement().toString();
+ return Optional.of(hostname.substring(0, hostname.length() - 1)).map(DomainName::of);
+ }
+ }
+ }
+ finally {
+ ctx.close();
+ }
+ }
+ catch (NamingException e) {
+ throw new RuntimeException(e);
+ }
+ return Optional.empty();
+ }
+
+}