diff options
author | HÃ¥kon Hallingstad <hakon@verizonmedia.com> | 2021-06-24 11:39:26 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-24 11:39:26 +0200 |
commit | 2bcd621f1957d13e7d14f7831af927009a891077 (patch) | |
tree | 91bc8d3de81ee46c6b1c99b3f28f122e43599994 | |
parent | 6c57e01614712bc23bff60cd055e9964d6e2cc40 (diff) | |
parent | 54792b76404ed27a95c3238a8850d720f6a38cc9 (diff) |
Merge pull request #18389 from vespa-engine/mpolden/defer-encryption
Add feature flag for deferring host encryption
3 files changed, 60 insertions, 3 deletions
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java index 54e3b5d8cc9..6e72ded830f 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java @@ -293,6 +293,12 @@ public class Flags { "Takes effect on next internal redeployment", APPLICATION_ID); + public static final UnboundListFlag<String> DEFER_APPLICATION_ENCRYPTION = defineListFlag( + "defer-application-encryption", List.of(), String.class, + List.of("mpolden", "hakonhall"), "2021-06-23", "2021-10-01", + "List of applications where encryption of their host should be deferred", + "Takes effect on next run of HostEncrypter"); + /** WARNING: public for testing: All flags should be defined in {@link Flags}. */ public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, List<String> owners, String createdAt, String expiresAt, String description, diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostEncrypter.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostEncrypter.java index 6d88e43630a..a4569f03d82 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostEncrypter.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostEncrypter.java @@ -2,10 +2,12 @@ package com.yahoo.vespa.hosted.provision.maintenance; import com.yahoo.component.Version; +import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.NodeType; import com.yahoo.jdisc.Metric; import com.yahoo.vespa.flags.Flags; import com.yahoo.vespa.flags.IntFlag; +import com.yahoo.vespa.flags.ListFlag; import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; import com.yahoo.vespa.hosted.provision.NodeRepository; @@ -20,6 +22,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Logger; +import java.util.stream.Collectors; /** * This maintainer triggers encryption of hosts that have unencrypted disk. @@ -36,10 +39,12 @@ public class HostEncrypter extends NodeRepositoryMaintainer { private static final Logger LOG = Logger.getLogger(HostEncrypter.class.getName()); private final IntFlag maxEncryptingHosts; + private final ListFlag<String> deferApplicationEncryption; public HostEncrypter(NodeRepository nodeRepository, Duration interval, Metric metric) { super(nodeRepository, interval, metric); this.maxEncryptingHosts = Flags.MAX_ENCRYPTING_HOSTS.bindTo(nodeRepository.flagSource()); + this.deferApplicationEncryption = Flags.DEFER_APPLICATION_ENCRYPTION.bindTo(nodeRepository.flagSource()); } @Override @@ -48,8 +53,6 @@ public class HostEncrypter extends NodeRepositoryMaintainer { NodeList allNodes = nodeRepository().nodes().list(); for (var nodeType : NodeType.values()) { if (!nodeType.isHost()) continue; - // TODO: Require a minimum number of proxies in Orchestrator. For now skip proxy hosts. - if (nodeType == NodeType.proxyhost) continue; if (upgradingVespa(allNodes, nodeType)) continue; unencryptedHosts(allNodes, nodeType).forEach(host -> encrypt(host, now)); } @@ -78,12 +81,18 @@ public class HostEncrypter extends NodeRepositoryMaintainer { // Encrypt hosts not containing stateful clusters with retiring nodes, up to limit List<Node> hostsToEncrypt = new ArrayList<>(hostLimit); + + Set<ApplicationId> deferredApplications = deferApplicationEncryption.value().stream() + .map(ApplicationId::fromSerializedForm) + .collect(Collectors.toSet()); NodeList candidates = hostsOfTargetType.state(Node.State.active) .not().encrypted() .not().encrypting() + .matching(host -> encryptHost(host, allNodes, deferredApplications)) // Require an OS version supporting encryption .matching(node -> node.status().osVersion().current() - .orElse(Version.emptyVersion).getMajor() >= 8); + .orElse(Version.emptyVersion) + .getMajor() >= 8); for (Node host : candidates) { if (hostsToEncrypt.size() == hostLimit) break; @@ -106,6 +115,17 @@ public class HostEncrypter extends NodeRepositoryMaintainer { return Math.max(0, limit - hosts.encrypting().size()); } + private boolean encryptHost(Node host, NodeList allNodes, Set<ApplicationId> deferredApplications) { + // TODO: Require a minimum number of proxies in Orchestrator. For now skip proxy hosts. + if (host.type() == NodeType.proxyhost) return false; + + Set<ApplicationId> applicationsOnHost = allNodes.childrenOf(host).stream() + .filter(node -> node.allocation().isPresent()) + .map(node -> node.allocation().get().owner()) + .collect(Collectors.toSet()); + return Collections.disjoint(applicationsOnHost, deferredApplications); + } + private void encrypt(Node host, Instant now) { LOG.info("Retiring and encrypting " + host); nodeRepository().nodes().encrypt(host.hostname(), Agent.HostEncrypter, now); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/HostEncrypterTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/HostEncrypterTest.java index 5cae181f87d..1e5d57263fa 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/HostEncrypterTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/HostEncrypterTest.java @@ -22,8 +22,10 @@ import java.time.Instant; import java.util.Comparator; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -47,6 +49,35 @@ public class HostEncrypterTest { } @Test + public void deferred_hosts_are_not_encrypted() { + int hostCount = 4; + int proxyHostCount = 1; + ApplicationId app1 = ApplicationId.from("t1", "a1", "i1"); + ApplicationId app2 = ApplicationId.from("t2", "a2", "i2"); + provisionHosts(hostCount); + deployApplication(app1); + deployApplication(app2); + + ApplicationId proxyHostApp = ApplicationId.from("hosted-vespa", "proxy-host", "default"); + List<Node> proxyHosts = tester.makeReadyNodes(proxyHostCount, "default", NodeType.proxyhost, 10); + tester.patchNodes(proxyHosts, (host) -> host.with(host.status().withOsVersion(host.status().osVersion().withCurrent(Optional.of(Version.fromString("8.0")))))); + tester.prepareAndActivateInfraApplication(proxyHostApp, NodeType.proxyhost); + + tester.flagSource() + .withIntFlag(Flags.MAX_ENCRYPTING_HOSTS.id(), hostCount + proxyHostCount) + .withListFlag(Flags.DEFER_APPLICATION_ENCRYPTION.id(), List.of(app2.serializedForm()), String.class); + encrypter.maintain(); + NodeList allNodes = tester.nodeRepository().nodes().list(); + NodeList encryptingHosts = allNodes.encrypting().parents(); + + assertEquals(1, encryptingHosts.size()); + assertEquals("Host of included application is encrypted", Set.of(app1), + allNodes.childrenOf(encryptingHosts.asList().get(0)).stream() + .map(node -> node.allocation().get().owner()) + .collect(Collectors.toSet())); + } + + @Test public void encrypt_hosts() { tester.flagSource().withIntFlag(Flags.MAX_ENCRYPTING_HOSTS.id(), 3); Supplier<NodeList> hosts = () -> tester.nodeRepository().nodes().list().nodeType(NodeType.host); |