diff options
author | Jon Marius Venstad <jonmv@users.noreply.github.com> | 2021-04-27 12:47:42 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-27 12:47:42 +0200 |
commit | 33b310ebd7fc46564ff6d9c15dcc0a559f7e34ff (patch) | |
tree | 696e03e557c9ee33995b1aca5120ac74556264b2 /node-repository/src/main/java/com | |
parent | 98adc735ed9ab9acb956bbd138f072b761c60c01 (diff) | |
parent | 29234349cc188aa8f7fc948b3208d83a87f461e3 (diff) |
Merge pull request #17611 from vespa-engine/jonmv/reapply-apache-client
Jonmv/reapply apache client
Diffstat (limited to 'node-repository/src/main/java/com')
6 files changed, 77 insertions, 47 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancer.java index 6f7b7c4d57d..a6bb57bef29 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/lb/LoadBalancer.java @@ -5,6 +5,7 @@ import com.yahoo.vespa.hosted.provision.maintenance.LoadBalancerExpirer; import java.time.Instant; import java.util.Objects; +import java.util.Optional; /** * Represents a load balancer for an application's cluster. This is immutable. @@ -14,15 +15,18 @@ import java.util.Objects; public class LoadBalancer { private final LoadBalancerId id; - private final LoadBalancerInstance instance; + private final Optional<LoadBalancerInstance> instance; private final State state; private final Instant changedAt; - public LoadBalancer(LoadBalancerId id, LoadBalancerInstance instance, State state, Instant changedAt) { + public LoadBalancer(LoadBalancerId id, Optional<LoadBalancerInstance> instance, State state, Instant changedAt) { this.id = Objects.requireNonNull(id, "id must be non-null"); this.instance = Objects.requireNonNull(instance, "instance must be non-null"); this.state = Objects.requireNonNull(state, "state must be non-null"); this.changedAt = Objects.requireNonNull(changedAt, "changedAt must be non-null"); + if (state == State.active && instance.isEmpty()) { + throw new IllegalArgumentException("Load balancer instance is required in state " + state); + } } /** An identifier for this load balancer. The ID is unique inside the zone */ @@ -31,7 +35,7 @@ public class LoadBalancer { } /** The instance associated with this */ - public LoadBalancerInstance instance() { + public Optional<LoadBalancerInstance> instance() { return instance; } @@ -58,7 +62,7 @@ public class LoadBalancer { } /** Returns a copy of this with instance set to given instance */ - public LoadBalancer with(LoadBalancerInstance instance) { + public LoadBalancer with(Optional<LoadBalancerInstance> instance) { return new LoadBalancer(id, instance, state, changedAt); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java index 2ef12177eaf..9665d8872de 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java @@ -12,6 +12,7 @@ import com.yahoo.vespa.hosted.provision.lb.LoadBalancerSpec; import com.yahoo.vespa.hosted.provision.persistence.CuratorDatabaseClient; import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; @@ -56,10 +57,10 @@ public class LoadBalancerExpirer extends NodeRepositoryMaintainer { /** Move reserved load balancer that have expired to inactive */ private void expireReserved() { - var expiry = nodeRepository().clock().instant().minus(reservedExpiry); - patchLoadBalancers(lb -> lb.state() == State.reserved && - lb.changedAt().isBefore(expiry), - lb -> db.writeLoadBalancer(lb.with(State.inactive, nodeRepository().clock().instant()))); + Instant now = nodeRepository().clock().instant(); + Instant expiry = now.minus(reservedExpiry); + patchLoadBalancers(lb -> lb.state() == State.reserved && lb.changedAt().isBefore(expiry), + lb -> db.writeLoadBalancer(lb.with(State.inactive, now))); } /** Deprovision inactive load balancers that have expired */ @@ -95,13 +96,14 @@ public class LoadBalancerExpirer extends NodeRepositoryMaintainer { var failed = new ArrayList<LoadBalancerId>(); var lastException = new AtomicReference<Exception>(); patchLoadBalancers(lb -> lb.state() == State.inactive, lb -> { + if (lb.instance().isEmpty()) return; var allocatedNodes = allocatedNodes(lb.id()).stream().map(Node::hostname).collect(Collectors.toSet()); - var reals = new LinkedHashSet<>(lb.instance().reals()); + var reals = new LinkedHashSet<>(lb.instance().get().reals()); // Remove any real no longer allocated to this application reals.removeIf(real -> !allocatedNodes.contains(real.hostname().value())); try { service.create(new LoadBalancerSpec(lb.id().application(), lb.id().cluster(), reals), true); - db.writeLoadBalancer(lb.with(lb.instance().withReals(reals))); + db.writeLoadBalancer(lb.with(lb.instance().map(instance -> instance.withReals(reals)))); } catch (Exception e) { failed.add(lb.id()); lastException.set(e); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/NodeAcl.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/NodeAcl.java index dddc535b36a..4cd01167293 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/NodeAcl.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/NodeAcl.java @@ -12,6 +12,7 @@ import com.yahoo.vespa.hosted.provision.lb.LoadBalancers; import java.util.Comparator; import java.util.LinkedHashSet; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.TreeSet; @@ -70,6 +71,7 @@ public class NodeAcl { loadBalancers.list(allocation.owner()).asList() .stream() .map(LoadBalancer::instance) + .flatMap(Optional::stream) .map(LoadBalancerInstance::networks) .forEach(trustedNetworks::addAll); }); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java index 66172521d4c..04c8f012b8b 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/LoadBalancerSerializer.java @@ -18,6 +18,7 @@ import java.io.UncheckedIOException; import java.time.Instant; import java.util.LinkedHashSet; import java.util.Optional; +import java.util.Set; import java.util.function.Function; /** @@ -50,21 +51,22 @@ public class LoadBalancerSerializer { Cursor root = slime.setObject(); root.setString(idField, loadBalancer.id().serializedForm()); - root.setString(hostnameField, loadBalancer.instance().hostname().toString()); + // TODO(mpolden): Stop writing this field for empty instance after 2021-06-01 + root.setString(hostnameField, loadBalancer.instance().map(instance -> instance.hostname().value()).orElse("")); root.setString(stateField, asString(loadBalancer.state())); root.setLong(changedAtField, loadBalancer.changedAt().toEpochMilli()); - loadBalancer.instance().dnsZone().ifPresent(dnsZone -> root.setString(dnsZoneField, dnsZone.id())); + loadBalancer.instance().flatMap(LoadBalancerInstance::dnsZone).ifPresent(dnsZone -> root.setString(dnsZoneField, dnsZone.id())); Cursor portArray = root.setArray(portsField); - loadBalancer.instance().ports().forEach(portArray::addLong); + loadBalancer.instance().ifPresent(instance -> instance.ports().forEach(portArray::addLong)); Cursor networkArray = root.setArray(networksField); - loadBalancer.instance().networks().forEach(networkArray::addString); + loadBalancer.instance().ifPresent(instance -> instance.networks().forEach(networkArray::addString)); Cursor realArray = root.setArray(realsField); - loadBalancer.instance().reals().forEach(real -> { + loadBalancer.instance().ifPresent(instance -> instance.reals().forEach(real -> { Cursor realObject = realArray.addObject(); realObject.setString(hostnameField, real.hostname().value()); realObject.setString(ipAddressField, real.ipAddress()); realObject.setLong(portField, real.port()); - }); + })); try { return SlimeUtils.toJsonBytes(slime); } catch (IOException e) { @@ -75,7 +77,7 @@ public class LoadBalancerSerializer { public static LoadBalancer fromJson(byte[] data) { Cursor object = SlimeUtils.jsonToSlime(data).get(); - var reals = new LinkedHashSet<Real>(); + Set<Real> reals = new LinkedHashSet<>(); object.field(realsField).traverse((ArrayTraverser) (i, realObject) -> { reals.add(new Real(HostName.from(realObject.field(hostnameField).asString()), realObject.field(ipAddressField).asString(), @@ -83,20 +85,19 @@ public class LoadBalancerSerializer { }); - var ports = new LinkedHashSet<Integer>(); + Set<Integer> ports = new LinkedHashSet<>(); object.field(portsField).traverse((ArrayTraverser) (i, port) -> ports.add((int) port.asLong())); - var networks = new LinkedHashSet<String>(); + Set<String> networks = new LinkedHashSet<>(); object.field(networksField).traverse((ArrayTraverser) (i, network) -> networks.add(network.asString())); + Optional<HostName> hostname = optionalString(object.field(hostnameField), Function.identity()).filter(s -> !s.isEmpty()).map(HostName::from); + Optional<DnsZone> dnsZone = optionalString(object.field(dnsZoneField), DnsZone::new); + Optional<LoadBalancerInstance> instance = hostname.map(h -> new LoadBalancerInstance(h, dnsZone, ports, + networks, reals)); + return new LoadBalancer(LoadBalancerId.fromSerializedForm(object.field(idField).asString()), - new LoadBalancerInstance( - HostName.from(object.field(hostnameField).asString()), - optionalString(object.field(dnsZoneField), DnsZone::new), - ports, - networks, - reals - ), + instance, stateFromString(object.field(stateField).asString()), Instant.ofEpochMilli(object.field(changedAtField).asLong())); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java index 09d19300c59..f53eb189ec1 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java @@ -170,18 +170,35 @@ public class LoadBalancerProvisioner { Instant now = nodeRepository.clock().instant(); Optional<LoadBalancer> loadBalancer = db.readLoadBalancer(id); if (loadBalancer.isEmpty() && activate) return; // Nothing to activate as this load balancer was never prepared - LoadBalancerInstance instance = provisionInstance(id, realsOf(nodes), loadBalancer); + + Set<Real> reals = realsOf(nodes); + Optional<LoadBalancerInstance> instance = provisionInstance(id, reals, loadBalancer); LoadBalancer newLoadBalancer; if (loadBalancer.isEmpty()) { newLoadBalancer = new LoadBalancer(id, instance, LoadBalancer.State.reserved, now); } else { - var newState = activate ? LoadBalancer.State.active : loadBalancer.get().state(); - newLoadBalancer = loadBalancer.get().with(instance).with(newState, now); + LoadBalancer.State state = activate && instance.isPresent() + ? LoadBalancer.State.active + : loadBalancer.get().state(); + newLoadBalancer = loadBalancer.get().with(instance).with(state, now); if (loadBalancer.get().state() != newLoadBalancer.state()) { log.log(Level.FINE, "Moving " + newLoadBalancer.id() + " to state " + newLoadBalancer.state()); } } - db.writeLoadBalancers(List.of(newLoadBalancer), transaction.nested()); + + if (activate) { + db.writeLoadBalancers(List.of(newLoadBalancer), transaction.nested()); + } else { + // Always store load balancer so that LoadBalancerExpirer can expire partially provisioned load balancers + db.writeLoadBalancer(newLoadBalancer); + } + + // Signal that load balancer is not ready yet + if (instance.isEmpty()) { + throw new LoadBalancerServiceException("Could not (re)configure " + id + ", targeting: " + + reals + ". The operation will be retried on next deployment", + null); + } } private void provision(ApplicationTransaction transaction, ClusterSpec.Id clusterId, NodeList nodes) { @@ -189,17 +206,18 @@ public class LoadBalancerProvisioner { } /** Provision or reconfigure a load balancer instance, if necessary */ - private LoadBalancerInstance provisionInstance(LoadBalancerId id, Set<Real> reals, - Optional<LoadBalancer> currentLoadBalancer) { + private Optional<LoadBalancerInstance> provisionInstance(LoadBalancerId id, Set<Real> reals, + Optional<LoadBalancer> currentLoadBalancer) { if (hasReals(currentLoadBalancer, reals)) return currentLoadBalancer.get().instance(); log.log(Level.FINE, "Creating " + id + ", targeting: " + reals); try { - return service.create(new LoadBalancerSpec(id.application(), id.cluster(), reals), - allowEmptyReals(currentLoadBalancer)); + return Optional.of(service.create(new LoadBalancerSpec(id.application(), id.cluster(), reals), + allowEmptyReals(currentLoadBalancer))); } catch (Exception e) { - throw new LoadBalancerServiceException("Failed to (re)configure " + id + ", targeting: " + - reals + ". The operation will be retried on next deployment", e); + log.log(Level.WARNING, "Could not (re)configure " + id + ", targeting: " + + reals + ". The operation will be retried on next deployment", e); } + return Optional.empty(); } /** Returns the nodes allocated to the given load balanced cluster */ @@ -246,7 +264,8 @@ public class LoadBalancerProvisioner { /** Returns whether load balancer has given reals */ private static boolean hasReals(Optional<LoadBalancer> loadBalancer, Set<Real> reals) { if (loadBalancer.isEmpty()) return false; - return loadBalancer.get().instance().reals().equals(reals); + if (loadBalancer.get().instance().isEmpty()) return false; + return loadBalancer.get().instance().get().reals().equals(reals); } /** Returns whether to allow given load balancer to have no reals */ @@ -273,5 +292,4 @@ public class LoadBalancerProvisioner { return cluster.combinedId().orElse(cluster.id()); } - } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersResponse.java index 1ef449555d9..bcd0af4e121 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersResponse.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersResponse.java @@ -9,6 +9,7 @@ import com.yahoo.slime.JsonFormat; import com.yahoo.slime.Slime; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.lb.LoadBalancer; +import com.yahoo.vespa.hosted.provision.lb.LoadBalancerInstance; import com.yahoo.vespa.hosted.provision.lb.LoadBalancerList; import com.yahoo.vespa.hosted.provision.node.filter.ApplicationFilter; @@ -65,21 +66,23 @@ public class LoadBalancersResponse extends HttpResponse { lbObject.setString("tenant", lb.id().application().tenant().value()); lbObject.setString("instance", lb.id().application().instance().value()); lbObject.setString("cluster", lb.id().cluster().value()); - lbObject.setString("hostname", lb.instance().hostname().value()); - lb.instance().dnsZone().ifPresent(dnsZone -> lbObject.setString("dnsZone", dnsZone.id())); + lb.instance().ifPresent(instance -> lbObject.setString("hostname", instance.hostname().value())); + lb.instance().flatMap(LoadBalancerInstance::dnsZone).ifPresent(dnsZone -> lbObject.setString("dnsZone", dnsZone.id())); Cursor networkArray = lbObject.setArray("networks"); - lb.instance().networks().forEach(networkArray::addString); + lb.instance().ifPresent(instance -> instance.networks().forEach(networkArray::addString)); Cursor portArray = lbObject.setArray("ports"); - lb.instance().ports().forEach(portArray::addLong); + lb.instance().ifPresent(instance -> instance.ports().forEach(portArray::addLong)); Cursor realArray = lbObject.setArray("reals"); - lb.instance().reals().forEach(real -> { - Cursor realObject = realArray.addObject(); - realObject.setString("hostname", real.hostname().value()); - realObject.setString("ipAddress", real.ipAddress()); - realObject.setLong("port", real.port()); + lb.instance().ifPresent(instance -> { + instance.reals().forEach(real -> { + Cursor realObject = realArray.addObject(); + realObject.setString("hostname", real.hostname().value()); + realObject.setString("ipAddress", real.ipAddress()); + realObject.setLong("port", real.port()); + }); }); }); |