summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAmund Bergland Kvalsvik <akvalsvik@verizonmedia.com>2020-07-20 10:40:08 +0200
committerAmund Bergland Kvalsvik <akvalsvik@verizonmedia.com>2020-07-20 10:40:08 +0200
commit02c7d5ed8bc25280ae8961cdb5d485a6dbe86f2e (patch)
tree7a87be3a7b6a78f65b40937316663c4bb2645f9f
parent89b290498b4f04e2c00ba2e9a00475f89d88eca3 (diff)
parentd0de44f2d5bf59a116edb10a09935e791cbbcd23 (diff)
Merge branch 'master' into adding-prometheus-unit-tests
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainer.java28
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java9
-rwxr-xr-xconfig-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java18
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerClusterTest.java11
-rwxr-xr-xconfig-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java7
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/DocprocBuilderTest.java14
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/BundleLoaderProperties.java15
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/ApplicationBundleLoader.java145
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/BundleManager.java236
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/BundleStarter.java40
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/DiskBundleInstaller.java15
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java18
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/PlatformBundleLoader.java72
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/testutil/HandlersConfigurerTestWrapper.java3
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/testutil/MockOsgiWrapper.java5
-rw-r--r--container-core/src/main/java/com/yahoo/osgi/MockOsgi.java6
-rw-r--r--container-core/src/main/java/com/yahoo/osgi/Osgi.java6
-rw-r--r--container-core/src/main/java/com/yahoo/osgi/OsgiImpl.java11
-rw-r--r--container-core/src/test/java/com/yahoo/container/core/config/ApplicationBundleLoaderTest.java (renamed from container-core/src/test/java/com/yahoo/container/core/config/BundleManagerTest.java)16
-rw-r--r--container-core/src/test/java/com/yahoo/container/core/config/PlatformBundleLoaderTest.java64
-rw-r--r--container-di/CMakeLists.txt2
-rw-r--r--container-di/src/main/java/com/yahoo/container/di/Container.java54
-rw-r--r--container-di/src/main/java/com/yahoo/container/di/Osgi.java6
-rw-r--r--container-di/src/main/resources/configdefinitions/application-bundles.def5
-rw-r--r--container-di/src/main/resources/configdefinitions/bundles.def2
-rw-r--r--container-di/src/main/resources/configdefinitions/platform-bundles.def5
-rw-r--r--container-di/src/test/java/com/yahoo/container/di/ContainerTest.java3
-rw-r--r--container-di/src/test/java/com/yahoo/container/di/ContainerTestBase.java5
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/application/OsgiFramework.java7
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/core/FelixFramework.java5
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java27
-rw-r--r--searchlib/src/tests/common/location/geo_location_test.cpp276
-rw-r--r--searchlib/src/vespa/searchlib/common/geo_location.h2
-rw-r--r--standalone-container/src/test/java/com/yahoo/container/standalone/StandaloneSubscriberTest.java15
-rw-r--r--vespajlib/src/main/java/com/yahoo/collections/PredicateSplit.java40
-rw-r--r--vespajlib/src/test/java/com/yahoo/collections/PredicateSplitTestCase.java30
36 files changed, 754 insertions, 469 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainer.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainer.java
index 547c05d2c9b..08f4e2fa12f 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainer.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerContainer.java
@@ -5,8 +5,8 @@ import com.yahoo.cloud.config.ZookeeperServerConfig;
import com.yahoo.component.ComponentSpecification;
import com.yahoo.config.model.api.container.ContainerServiceType;
import com.yahoo.config.model.producer.AbstractConfigProducer;
-import com.yahoo.container.BundlesConfig;
import com.yahoo.container.bundle.BundleInstantiationSpecification;
+import com.yahoo.container.di.config.PlatformBundlesConfig;
import com.yahoo.osgi.provider.model.ComponentModel;
import com.yahoo.vespa.config.content.FleetcontrollerConfig;
import com.yahoo.vespa.model.application.validation.RestartConfigs;
@@ -14,18 +14,17 @@ import com.yahoo.vespa.model.container.Container;
import com.yahoo.vespa.model.container.component.AccessLogComponent;
import com.yahoo.vespa.model.container.component.Component;
import com.yahoo.vespa.model.container.component.Handler;
+import com.yahoo.vespa.model.container.xml.PlatformBundles;
import java.util.Set;
import java.util.TreeSet;
-import static com.yahoo.vespa.defaults.Defaults.getDefaults;
-
/**
* Container implementation for cluster-controllers
*/
@RestartConfigs({FleetcontrollerConfig.class, ZookeeperServerConfig.class})
public class ClusterControllerContainer extends Container implements
- BundlesConfig.Producer,
+ PlatformBundlesConfig.Producer,
ZookeeperServerConfig.Producer
{
private static final ComponentSpecification CLUSTERCONTROLLER_BUNDLE = new ComponentSpecification("clustercontroller-apps");
@@ -55,11 +54,12 @@ public class ClusterControllerContainer extends Container implements
}
addComponent(new AccessLogComponent(AccessLogComponent.AccessLogType.jsonAccessLog, "controller", isHosted));
- addFileBundle("lib/jars/clustercontroller-apps-jar-with-dependencies.jar");
- addFileBundle("lib/jars/clustercontroller-apputil-jar-with-dependencies.jar");
- addFileBundle("lib/jars/clustercontroller-core-jar-with-dependencies.jar");
- addFileBundle("lib/jars/clustercontroller-utils-jar-with-dependencies.jar");
- addFileBundle("lib/jars/zookeeper-server-jar-with-dependencies.jar");
+ // TODO: Why are bundles added here instead of in the cluster?
+ addFileBundle("clustercontroller-apps");
+ addFileBundle("clustercontroller-apputil");
+ addFileBundle("clustercontroller-core");
+ addFileBundle("clustercontroller-utils");
+ addFileBundle("zookeeper-server");
}
@Override
@@ -82,8 +82,8 @@ public class ClusterControllerContainer extends Container implements
super.addHandler(h);
}
- private void addFileBundle(String bundlePath) {
- bundles.add("file:" + getDefaults().underVespaHome(bundlePath));
+ private void addFileBundle(String bundleName) {
+ bundles.add(PlatformBundles.absoluteBundlePath(bundleName).toString());
}
private ComponentModel createComponentModel(String id, String className, ComponentSpecification bundle) {
@@ -102,10 +102,8 @@ public class ClusterControllerContainer extends Container implements
}
@Override
- public void getConfig(BundlesConfig.Builder builder) {
- for (String bundle : bundles) {
- builder.bundle(bundle);
- }
+ public void getConfig(PlatformBundlesConfig.Builder builder) {
+ bundles.forEach(builder::bundlePaths);
}
@Override
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java
index 9ce5fdfcc04..b0ac02d0fe8 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ApplicationContainerCluster.java
@@ -8,8 +8,8 @@ import com.yahoo.config.FileReference;
import com.yahoo.config.application.api.ComponentInfo;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.producer.AbstractConfigProducer;
-import com.yahoo.container.BundlesConfig;
import com.yahoo.container.bundle.BundleInstantiationSpecification;
+import com.yahoo.container.di.config.ApplicationBundlesConfig;
import com.yahoo.container.handler.ThreadpoolConfig;
import com.yahoo.container.handler.metrics.MetricsProxyApiConfig;
import com.yahoo.container.handler.metrics.MetricsV2Handler;
@@ -46,7 +46,7 @@ import java.util.stream.Stream;
* @author gjoranv
*/
public final class ApplicationContainerCluster extends ContainerCluster<ApplicationContainer> implements
- BundlesConfig.Producer,
+ ApplicationBundlesConfig.Producer,
QrStartConfig.Producer,
RankProfilesConfig.Producer,
RankingConstantsConfig.Producer,
@@ -188,10 +188,9 @@ public final class ApplicationContainerCluster extends ContainerCluster<Applicat
public Optional<Integer> getMemoryPercentage() { return Optional.ofNullable(memoryPercentage); }
@Override
- public void getConfig(BundlesConfig.Builder builder) {
+ public void getConfig(ApplicationBundlesConfig.Builder builder) {
applicationBundles.stream().map(FileReference::value)
- .forEach(builder::bundle);
- super.getConfig(builder);
+ .forEach(builder::bundles);
}
@Override
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java
index 5127616ad5e..240157fb7aa 100755
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java
@@ -12,12 +12,12 @@ import com.yahoo.config.model.ApplicationConfigProducerRoot;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.producer.AbstractConfigProducer;
import com.yahoo.config.provision.Zone;
-import com.yahoo.container.BundlesConfig;
import com.yahoo.container.ComponentsConfig;
import com.yahoo.container.QrSearchersConfig;
import com.yahoo.container.bundle.BundleInstantiationSpecification;
import com.yahoo.container.core.ApplicationMetadataConfig;
import com.yahoo.container.core.document.ContainerDocumentConfig;
+import com.yahoo.container.di.config.PlatformBundlesConfig;
import com.yahoo.container.handler.ThreadpoolConfig;
import com.yahoo.container.jdisc.JdiscBindingsConfig;
import com.yahoo.container.jdisc.config.HealthMonitorConfig;
@@ -69,8 +69,6 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
-import static com.yahoo.container.core.BundleLoaderProperties.DISK_BUNDLE_PREFIX;
-
/**
* Parent class for all container cluster types.
*
@@ -87,7 +85,7 @@ public abstract class ContainerCluster<CONTAINER extends Container>
ContainerDocumentConfig.Producer,
HealthMonitorConfig.Producer,
ApplicationMetadataConfig.Producer,
- BundlesConfig.Producer,
+ PlatformBundlesConfig.Producer,
IndexInfoConfig.Producer,
IlscriptsConfig.Producer,
SchemamappingConfig.Producer,
@@ -464,6 +462,7 @@ public abstract class ContainerCluster<CONTAINER extends Container>
/**
* Adds a bundle present at a known location at the target container nodes.
+ * Note that the set of platform bundles cannot change during the jdisc container's lifetime.
*
* @param bundlePath usually an absolute path, e.g. '$VESPA_HOME/lib/jars/foo.jar'
*/
@@ -472,13 +471,10 @@ public abstract class ContainerCluster<CONTAINER extends Container>
}
@Override
- public void getConfig(BundlesConfig.Builder builder) {
- platformBundles.stream() .map(ContainerCluster::toFileReferenceString)
- .forEach(builder::bundle);
- }
-
- private static String toFileReferenceString(Path path) {
- return DISK_BUNDLE_PREFIX + path.toString();
+ public void getConfig(PlatformBundlesConfig.Builder builder) {
+ platformBundles.stream()
+ .map(Path::toString)
+ .forEach(builder::bundlePaths);
}
@Override
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerClusterTest.java
index bed77bd5c77..34f06519ac9 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerClusterTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerClusterTest.java
@@ -15,8 +15,8 @@ import ai.vespa.metricsproxy.metric.dimensions.PublicDimensions;
import com.yahoo.component.ComponentSpecification;
import com.yahoo.config.model.test.MockApplicationPackage;
import com.yahoo.config.provision.Zone;
-import com.yahoo.container.BundlesConfig;
import com.yahoo.container.core.ApplicationMetadataConfig;
+import com.yahoo.container.di.config.PlatformBundlesConfig;
import com.yahoo.search.config.QrStartConfig;
import com.yahoo.vespa.model.VespaModel;
import com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyContainerCluster.AppDimensionNames;
@@ -44,7 +44,6 @@ import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -56,11 +55,11 @@ public class MetricsProxyContainerClusterTest {
@Test
public void metrics_proxy_bundle_is_included_in_bundles_config() {
VespaModel model = getModel(servicesWithAdminOnly(), self_hosted);
- var builder = new BundlesConfig.Builder();
+ var builder = new PlatformBundlesConfig.Builder();
model.getConfig(builder, CLUSTER_CONFIG_ID);
- BundlesConfig config = builder.build();
- assertEquals(1, config.bundle().size());
- assertThat(config.bundle(0).value(), endsWith(METRICS_PROXY_BUNDLE_FILE.toString()));
+ PlatformBundlesConfig config = builder.build();
+ assertEquals(1, config.bundlePaths().size());
+ assertThat(config.bundlePaths(0), endsWith(METRICS_PROXY_BUNDLE_FILE.toString()));
}
@Test
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java
index 03c05af1145..97359b392a5 100755
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java
@@ -4,7 +4,6 @@ package com.yahoo.vespa.model.container;
import com.yahoo.cloud.config.ClusterInfoConfig;
import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.cloud.config.RoutingProviderConfig;
-import com.yahoo.config.FileReference;
import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.deploy.TestProperties;
@@ -16,7 +15,7 @@ import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.Zone;
import com.yahoo.config.provisioning.FlavorsConfig;
-import com.yahoo.container.BundlesConfig;
+import com.yahoo.container.di.config.PlatformBundlesConfig;
import com.yahoo.container.handler.ThreadpoolConfig;
import com.yahoo.search.config.QrStartConfig;
import com.yahoo.vespa.model.Host;
@@ -292,9 +291,9 @@ public class ContainerClusterTest {
.zone(zone).build();
MockRoot root = new MockRoot("foo", state);
ApplicationContainerCluster cluster = new ApplicationContainerCluster(root, "container0", "container1", state);
- BundlesConfig.Builder bundleBuilder = new BundlesConfig.Builder();
+ var bundleBuilder = new PlatformBundlesConfig.Builder();
cluster.getConfig(bundleBuilder);
- List<String> installedBundles = bundleBuilder.build().bundle().stream().map(FileReference::value).collect(Collectors.toList());
+ List<String> installedBundles = bundleBuilder.build().bundlePaths();
assertEquals(expectedBundleNames.size(), installedBundles.size());
assertThat(installedBundles, containsInAnyOrder(
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/DocprocBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/DocprocBuilderTest.java
index eda90b03147..e9048cf7863 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/DocprocBuilderTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/DocprocBuilderTest.java
@@ -3,17 +3,16 @@ package com.yahoo.vespa.model.container.xml;
import com.yahoo.config.docproc.DocprocConfig;
import com.yahoo.config.docproc.SchemamappingConfig;
-import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.builder.xml.test.DomBuilderTest;
-import com.yahoo.container.BundlesConfig;
+import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.container.ComponentsConfig;
import com.yahoo.container.core.ChainsConfig;
import com.yahoo.container.jdisc.ContainerMbusConfig;
import com.yahoo.document.config.DocumentmanagerConfig;
import com.yahoo.search.config.QrStartConfig;
import com.yahoo.vespa.model.HostPorts;
-import com.yahoo.vespa.model.container.ApplicationContainerCluster;
import com.yahoo.vespa.model.container.ApplicationContainer;
+import com.yahoo.vespa.model.container.ApplicationContainerCluster;
import com.yahoo.vespa.model.container.ContainerModel;
import com.yahoo.vespa.model.container.docproc.DocprocChain;
import com.yahoo.vespa.model.container.docproc.DocumentProcessor;
@@ -30,8 +29,8 @@ import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
/**
@@ -46,7 +45,6 @@ public class DocprocBuilderTest extends DomBuilderTest {
private ContainerMbusConfig containerMbusConfig;
private ComponentsConfig componentsConfig;
private ChainsConfig chainsConfig;
- private BundlesConfig bundlesConfig;
private SchemamappingConfig schemamappingConfig;
private DocprocConfig docprocConfig;
private QrStartConfig qrStartConfig;
@@ -64,7 +62,6 @@ public class DocprocBuilderTest extends DomBuilderTest {
cluster.getConfigId() + "/component/com.yahoo.docproc.jdisc.DocumentProcessingHandler");
documentmanagerConfig = root.getConfig(DocumentmanagerConfig.class, cluster.getConfigId());
- bundlesConfig = root.getConfig(BundlesConfig.class, cluster.getConfigId());
schemamappingConfig = root.getConfig(SchemamappingConfig.class, cluster.getContainers().get(0).getConfigId());
qrStartConfig = root.getConfig(QrStartConfig.class, cluster.getConfigId());
docprocConfig = root.getConfig(DocprocConfig.class, cluster.getConfigId());
@@ -207,11 +204,6 @@ public class DocprocBuilderTest extends DomBuilderTest {
}
@Test
- public void testBundlesConfig() {
- assertTrue(bundlesConfig.bundle().isEmpty());
- }
-
- @Test
public void testSchemaMappingConfig() {
assertTrue(schemamappingConfig.fieldmapping().isEmpty());
}
diff --git a/container-core/src/main/java/com/yahoo/container/core/BundleLoaderProperties.java b/container-core/src/main/java/com/yahoo/container/core/BundleLoaderProperties.java
deleted file mode 100644
index ee12c7d4c9f..00000000000
--- a/container-core/src/main/java/com/yahoo/container/core/BundleLoaderProperties.java
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.core;
-
-/**
- * @author gjoranv
- */
-public interface BundleLoaderProperties {
-
- // TODO: This should be removed. The prefix is used to separate the bundles in BundlesConfig
- // into those that are transferred with filedistribution and those that are preinstalled
- // on disk. Instead, the model should have put them in two different configs. I.e. create a new
- // config 'preinstalled-bundles.def'.
- String DISK_BUNDLE_PREFIX = "file:";
-
-}
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/ApplicationBundleLoader.java b/container-core/src/main/java/com/yahoo/container/core/config/ApplicationBundleLoader.java
new file mode 100644
index 00000000000..3e8ef65ade6
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/container/core/config/ApplicationBundleLoader.java
@@ -0,0 +1,145 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.core.config;
+
+import com.yahoo.config.FileReference;
+import com.yahoo.container.Container;
+import com.yahoo.filedistribution.fileacquirer.FileAcquirer;
+import com.yahoo.osgi.Osgi;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.wiring.BundleRevision;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+/**
+ * Manages the set of installed and active/inactive bundles.
+ *
+ * @author gjoranv
+ * @author Tony Vaagenes
+ */
+public class ApplicationBundleLoader {
+
+ /* Map of file refs of active bundles (not scheduled for uninstall) to the installed bundle.
+ *
+ * Used to:
+ * 1. Avoid installing already installed bundles. Just an optimization, installing the same bundle location is a NOP
+ * 2. Start bundles (all are started every time)
+ * 3. Calculate the set of bundles to uninstall
+ */
+ private final Map<FileReference, Bundle> reference2Bundle = new LinkedHashMap<>();
+
+ private final Logger log = Logger.getLogger(ApplicationBundleLoader.class.getName());
+ private final Osgi osgi;
+
+ // A custom bundle installer for non-disk bundles, to be used for testing
+ private BundleInstaller customBundleInstaller = null;
+
+ public ApplicationBundleLoader(Osgi osgi) {
+ this.osgi = osgi;
+ }
+
+ /**
+ * Installs the given set of bundles and returns the set of bundles that is no longer used
+ * by the application, and should therefore be scheduled for uninstall.
+ */
+ public synchronized Set<Bundle> useBundles(List<FileReference> newFileReferences) {
+
+ Set<FileReference> obsoleteReferences = getObsoleteFileReferences(newFileReferences);
+ Set<Bundle> bundlesToUninstall = getObsoleteBundles(obsoleteReferences);
+ log.info("Bundles to schedule for uninstall: " + bundlesToUninstall);
+
+ osgi.allowDuplicateBundles(bundlesToUninstall);
+ removeInactiveFileReferences(obsoleteReferences);
+
+ installBundles(newFileReferences);
+ BundleStarter.startBundles(reference2Bundle.values());
+ log.info(installedBundlesMessage());
+
+ return bundlesToUninstall;
+ }
+
+ private Set<FileReference> getObsoleteFileReferences(List<FileReference> newReferences) {
+ Set<FileReference> obsoleteReferences = new HashSet<>(reference2Bundle.keySet());
+ obsoleteReferences.removeAll(newReferences);
+ return obsoleteReferences;
+ }
+
+
+ /**
+ * Returns the bundles that will not be retained by the new application generation.
+ */
+ private Set<Bundle> getObsoleteBundles(Set<FileReference> obsoleteReferences) {
+ return obsoleteReferences.stream().map(reference2Bundle::get).collect(Collectors.toSet());
+ }
+
+ private void removeInactiveFileReferences(Set<FileReference> fileReferencesToRemove) {
+ fileReferencesToRemove.forEach(reference2Bundle::remove);
+ }
+
+ private void installBundles(List<FileReference> references) {
+ Set<FileReference> bundlesToInstall = new HashSet<>(references);
+
+ // This is just an optimization, as installing a bundle with the same location id returns the already installed bundle.
+ bundlesToInstall.removeAll(reference2Bundle.keySet());
+
+ if (!bundlesToInstall.isEmpty()) {
+ FileAcquirer fileAcquirer = Container.get().getFileAcquirer();
+ boolean hasFileDistribution = (fileAcquirer != null);
+ if (hasFileDistribution) {
+ installWithFileDistribution(bundlesToInstall, new FileAcquirerBundleInstaller(fileAcquirer));
+ } else if (customBundleInstaller != null) {
+ installWithFileDistribution(bundlesToInstall, customBundleInstaller);
+ } else {
+ log.warning("Can't retrieve bundles since file distribution is disabled.");
+ }
+ }
+ }
+
+ private void installWithFileDistribution(Set<FileReference> bundlesToInstall, BundleInstaller bundleInstaller) {
+ for (FileReference reference : bundlesToInstall) {
+ try {
+ log.info("Installing bundle with reference '" + reference.value() + "'");
+ List<Bundle> bundles = bundleInstaller.installBundles(reference, osgi);
+
+ // If more than one bundle was installed, and the OSGi framework is the real Felix one,
+ // it means that the X-JDisc-Preinstall-Bundle header was used.
+ // However, test osgi frameworks may return multiple bundles when installing a single bundle.
+ if (bundles.size() > 1 && osgi.hasFelixFramework()) {
+ // TODO: remove if-statement below when the last model with preinstall has rolled out of hosted
+ if (! bundles.get(0).getSymbolicName().equals("config-model-fat-amended"))
+ throw new RuntimeException("Bundle '" + bundles.get(0).getSymbolicName() + "' tried to pre-install bundles from disk.");
+ }
+ reference2Bundle.put(reference, bundles.get(0));
+ }
+ catch(Exception e) {
+ throw new RuntimeException("Could not install bundle with reference '" + reference + "'", e);
+ }
+ }
+ }
+
+ private String installedBundlesMessage() {
+ StringBuilder sb = new StringBuilder("Installed bundles: {" );
+ for (Bundle b : osgi.getBundles())
+ sb.append("[" + b.getBundleId() + "]" + b.getSymbolicName() + ":" + b.getVersion() + ", ");
+ sb.setLength(sb.length() - 2);
+ sb.append("}");
+ return sb.toString();
+ }
+
+ // Only for testing
+ void useCustomBundleInstaller(BundleInstaller bundleInstaller) {
+ customBundleInstaller = bundleInstaller;
+ }
+
+ // Only for testing
+ List<FileReference> getActiveFileReferences() {
+ return new ArrayList<>(reference2Bundle.keySet());
+ }
+
+}
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/BundleManager.java b/container-core/src/main/java/com/yahoo/container/core/config/BundleManager.java
deleted file mode 100644
index 406d68408e3..00000000000
--- a/container-core/src/main/java/com/yahoo/container/core/config/BundleManager.java
+++ /dev/null
@@ -1,236 +0,0 @@
-// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.core.config;
-
-import com.yahoo.collections.PredicateSplit;
-import com.yahoo.config.FileReference;
-import com.yahoo.container.Container;
-import com.yahoo.filedistribution.fileacquirer.FileAcquirer;
-import com.yahoo.osgi.Osgi;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.wiring.BundleRevision;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.logging.Logger;
-import java.util.stream.Collectors;
-
-import static com.yahoo.collections.PredicateSplit.partition;
-import static com.yahoo.container.core.BundleLoaderProperties.DISK_BUNDLE_PREFIX;
-
-/**
- * Manages the set of installed and active/inactive bundles.
- *
- * @author gjoranv
- * @author Tony Vaagenes
- */
-public class BundleManager {
-
- /* Map of file refs of active bundles (not scheduled for uninstall) to a list of all bundles that were installed
- * (pre-install directive) by the bundle pointed to by the file ref (including itself).
- *
- * Used to:
- * 1. Avoid installing already installed bundles. Just an optimization, installing the same bundle location is a NOP
- * 2. Start bundles (all are started every time)
- * 3. Calculate the set of bundles to uninstall
- */
- private final Map<FileReference, List<Bundle>> reference2Bundles = new LinkedHashMap<>();
-
- private final Logger log = Logger.getLogger(BundleManager.class.getName());
- private final Osgi osgi;
-
- // A custom bundle installer for non-disk bundles, to be used for testing
- private BundleInstaller customBundleInstaller = null;
-
- public BundleManager(Osgi osgi) {
- this.osgi = osgi;
- }
-
- /**
- * Installs the given set of bundles and returns the set of bundles that is no longer used
- * by the application, and should therefore be scheduled for uninstall.
- */
- public synchronized Set<Bundle> use(List<FileReference> newFileReferences) {
- // Must be done before allowing duplicates because allowed duplicates affect osgi.getCurrentBundles
- Set<Bundle> bundlesToUninstall = getObsoleteBundles(newFileReferences);
-
- Set<FileReference> obsoleteReferences = getObsoleteFileReferences(newFileReferences);
- allowDuplicateBundles(obsoleteReferences);
- removeInactiveFileReferences(obsoleteReferences);
-
- installBundles(newFileReferences);
- startBundles();
-
- bundlesToUninstall.removeAll(allActiveBundles());
- log.info("Bundles to schedule for uninstall: " + bundlesToUninstall);
-
- log.info(installedBundlesMessage());
- return bundlesToUninstall;
- }
-
- /**
- * Returns the bundles that are not assumed to be retained by the new application generation.
- * Note that at this point we don't yet know the full set of new bundles, because of the potential
- * pre-install directives in the new bundles. However, only "disk bundles" (file:) can be listed
- * in the pre-install directive, so we know about all the obsolete application bundles.
- */
- private Set<Bundle> getObsoleteBundles(List<FileReference> newReferences) {
- Set<Bundle> bundlesToRemove = new HashSet<>(osgi.getCurrentBundles());
-
- for (FileReference fileReferenceToKeep : newReferences) {
- if (reference2Bundles.containsKey(fileReferenceToKeep)) {
- bundlesToRemove.removeAll(reference2Bundles.get(fileReferenceToKeep));
- }
- }
- bundlesToRemove.removeAll(osgi.getInitialBundles());
- return bundlesToRemove;
- }
-
-
- private Set<FileReference> getObsoleteFileReferences(List<FileReference> newReferences) {
- Set<FileReference> obsoleteReferences = new HashSet<>(reference2Bundles.keySet());
- obsoleteReferences.removeAll(newReferences);
- return obsoleteReferences;
- }
-
- /**
- * Allow duplicates (bsn+version) for each bundle that corresponds to obsolete file references,
- * and avoid allowing duplicates for bundles that were installed via the
- * X-JDisc-Preinstall-Bundle directive. These bundles are always "disk bundles" (library
- * bundles installed on the node, and not transferred via file distribution).
- * Such bundles will never have duplicates because they always have the same location id.
- */
- private void allowDuplicateBundles(Set<FileReference> obsoleteReferences) {
- // The bundle at index 0 for each file reference always corresponds to the bundle at the file reference location
- Set<Bundle> allowedDuplicates = obsoleteReferences.stream()
- .filter(reference -> ! isDiskBundle(reference))
- .map(reference -> reference2Bundles.get(reference).get(0))
- .collect(Collectors.toSet());
-
- log.info(() -> allowedDuplicates.isEmpty() ? "" : "Adding bundles to allowed duplicates: " + allowedDuplicates);
- osgi.allowDuplicateBundles(allowedDuplicates);
- }
-
- /**
- * Cleans up the map of active file references
- */
- private void removeInactiveFileReferences(Set<FileReference> fileReferencesToRemove) {
- // Clean up the map of active bundles
- fileReferencesToRemove.forEach(reference2Bundles::remove);
- }
-
- private void installBundles(List<FileReference> references) {
- Set<FileReference> bundlesToInstall = new HashSet<>(references);
-
- // This is just an optimization, as installing a bundle with the same location id returns the already installed bundle.
- bundlesToInstall.removeAll(reference2Bundles.keySet());
-
- PredicateSplit<FileReference> bundlesToInstall_isDisk = partition(bundlesToInstall, BundleManager::isDiskBundle);
- installBundlesFromDisk(bundlesToInstall_isDisk.trueValues);
- installBundlesFromFileDistribution(bundlesToInstall_isDisk.falseValues);
- }
-
- private static boolean isDiskBundle(FileReference fileReference) {
- return fileReference.value().startsWith(DISK_BUNDLE_PREFIX);
- }
-
- private void installBundlesFromDisk(List<FileReference> bundlesToInstall) {
- for (FileReference reference : bundlesToInstall) {
- try {
- installBundleFromDisk(reference);
- }
- catch(Exception e) {
- throw new RuntimeException("Could not install bundle '" + reference + "'", e);
- }
- }
- }
-
- private void installBundlesFromFileDistribution(List<FileReference> bundlesToInstall) {
- if (!bundlesToInstall.isEmpty()) {
- FileAcquirer fileAcquirer = Container.get().getFileAcquirer();
- boolean hasFileDistribution = (fileAcquirer != null);
- if (hasFileDistribution) {
- installWithFileDistribution(bundlesToInstall, new FileAcquirerBundleInstaller(fileAcquirer));
- } else if (customBundleInstaller != null) {
- installWithFileDistribution(bundlesToInstall, customBundleInstaller);
- } else {
- log.warning("Can't retrieve bundles since file distribution is disabled.");
- }
- }
- }
-
- private void installBundleFromDisk(FileReference reference) {
- log.info("Installing bundle from disk with reference '" + reference.value() + "'");
-
- var bundleInstaller = new DiskBundleInstaller();
- List<Bundle> bundles = bundleInstaller.installBundles(reference, osgi);
- reference2Bundles.put(reference, bundles);
- }
-
- private void installWithFileDistribution(List<FileReference> bundlesToInstall, BundleInstaller bundleInstaller) {
- for (FileReference reference : bundlesToInstall) {
- try {
- log.info("Installing bundle with reference '" + reference.value() + "'");
- List<Bundle> bundles = bundleInstaller.installBundles(reference, osgi);
- reference2Bundles.put(reference, bundles);
- }
- catch(Exception e) {
- throw new RuntimeException("Could not install bundle '" + reference + "'", e);
- }
- }
- }
-
- /**
- * Resolves and starts (calls the Bundles BundleActivator) all bundles. Bundle resolution must take place
- * after all bundles are installed to ensure that the framework can resolve dependencies between bundles.
- */
- private void startBundles() {
- for (List<Bundle> bundles : reference2Bundles.values()) {
- for (Bundle bundle : bundles) {
- try {
- if ( ! isFragment(bundle))
- bundle.start(); // NOP for already ACTIVE bundles
- } catch(Exception e) {
- throw new RuntimeException("Could not start bundle '" + bundle.getSymbolicName() + "'", e);
- }
- }
- }
- }
-
- private boolean isFragment(Bundle bundle) {
- BundleRevision bundleRevision = bundle.adapt(BundleRevision.class);
- if (bundleRevision == null)
- throw new NullPointerException("Null bundle revision means that bundle has probably been uninstalled: " +
- bundle.getSymbolicName() + ":" + bundle.getVersion());
- return (bundleRevision.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0;
- }
-
- private Set<Bundle> allActiveBundles() {
- return reference2Bundles.keySet().stream()
- .flatMap(reference -> reference2Bundles.get(reference).stream())
- .collect(Collectors.toSet());
- }
-
- private String installedBundlesMessage() {
- StringBuilder sb = new StringBuilder("Installed bundles: {" );
- for (Bundle b : osgi.getBundles())
- sb.append("[" + b.getBundleId() + "]" + b.getSymbolicName() + ":" + b.getVersion() + ", ");
- sb.setLength(sb.length() - 2);
- sb.append("}");
- return sb.toString();
- }
-
- // Only for testing
- void useCustomBundleInstaller(BundleInstaller bundleInstaller) {
- customBundleInstaller = bundleInstaller;
- }
-
- // Only for testing
- List<FileReference> getActiveFileReferences() {
- return new ArrayList<>(reference2Bundles.keySet());
- }
-
-}
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/BundleStarter.java b/container-core/src/main/java/com/yahoo/container/core/config/BundleStarter.java
new file mode 100644
index 00000000000..4a87c27b990
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/container/core/config/BundleStarter.java
@@ -0,0 +1,40 @@
+package com.yahoo.container.core.config;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.wiring.BundleRevision;
+
+import java.util.Collection;
+
+/**
+ * Utility to start a collection of bundles.
+ *
+ * @author gjoranv
+ */
+public class BundleStarter {
+
+ private BundleStarter() { }
+
+ /**
+ * Resolves and starts (calls the Bundles BundleActivator) all bundles. Bundle resolution must take place
+ * after all bundles are installed to ensure that the framework can resolve dependencies between bundles.
+ */
+ static void startBundles(Collection<Bundle> bundles) {
+ for (var bundle : bundles) {
+ try {
+ if ( ! isFragment(bundle))
+ bundle.start(); // NOP for already ACTIVE bundles
+ } catch(Exception e) {
+ throw new RuntimeException("Could not start bundle '" + bundle.getSymbolicName() + "'", e);
+ }
+ }
+ }
+
+ private static boolean isFragment(Bundle bundle) {
+ BundleRevision bundleRevision = bundle.adapt(BundleRevision.class);
+ if (bundleRevision == null)
+ throw new NullPointerException("Null bundle revision means that bundle has probably been uninstalled: " +
+ bundle.getSymbolicName() + ":" + bundle.getVersion());
+ return (bundleRevision.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0;
+ }
+
+}
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/DiskBundleInstaller.java b/container-core/src/main/java/com/yahoo/container/core/config/DiskBundleInstaller.java
index 3edabe9f861..a4cce2e38db 100644
--- a/container-core/src/main/java/com/yahoo/container/core/config/DiskBundleInstaller.java
+++ b/container-core/src/main/java/com/yahoo/container/core/config/DiskBundleInstaller.java
@@ -1,28 +1,21 @@
// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.container.core.config;
-import com.yahoo.config.FileReference;
import com.yahoo.osgi.Osgi;
import org.osgi.framework.Bundle;
import java.io.File;
import java.util.List;
-import static com.yahoo.container.core.BundleLoaderProperties.DISK_BUNDLE_PREFIX;
-
/**
* @author gjoranv
*/
-public class DiskBundleInstaller implements BundleInstaller {
-
- @Override
- public List<Bundle> installBundles(FileReference reference, Osgi osgi) {
- assert(reference.value().startsWith(DISK_BUNDLE_PREFIX));
- String referenceFileName = reference.value().substring(DISK_BUNDLE_PREFIX.length());
+public class DiskBundleInstaller {
- File file = new File(referenceFileName);
+ public List<Bundle> installBundles(String bundlePath, Osgi osgi) {
+ File file = new File(bundlePath);
if ( ! file.exists()) {
- throw new IllegalArgumentException("Reference '" + reference.value() + "' not found on disk.");
+ throw new IllegalArgumentException("Bundle file '" + bundlePath + "' not found on disk.");
}
return osgi.install(file.getAbsolutePath());
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java b/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java
index b983cbcfe37..a58dff13f09 100644
--- a/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java
+++ b/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java
@@ -101,12 +101,16 @@ public class HandlersConfigurerDi {
private static class ContainerAndDiOsgi extends OsgiImpl implements OsgiWrapper {
private final OsgiFramework osgiFramework;
- private final BundleManager bundleManager;
+ private final ApplicationBundleLoader applicationBundleLoader;
+ private final PlatformBundleLoader platformBundleLoader;
public ContainerAndDiOsgi(OsgiFramework osgiFramework) {
super(osgiFramework);
this.osgiFramework = osgiFramework;
- bundleManager = new BundleManager(new OsgiImpl(osgiFramework));
+
+ OsgiImpl osgi = new OsgiImpl(osgiFramework);
+ applicationBundleLoader = new ApplicationBundleLoader(osgi);
+ platformBundleLoader = new PlatformBundleLoader(osgi);
}
@@ -131,9 +135,15 @@ public class HandlersConfigurerDi {
}
@Override
- public Set<Bundle> useBundles(Collection<FileReference> bundles) {
+ public void installPlatformBundles(Collection<String> bundlePaths) {
+ log.fine("Installing platform bundles.");
+ platformBundleLoader.useBundles(new ArrayList<>(bundlePaths));
+ }
+
+ @Override
+ public Set<Bundle> useApplicationBundles(Collection<FileReference> bundles) {
log.info("Installing bundles from the latest application");
- return bundleManager.use(new ArrayList<>(bundles));
+ return applicationBundleLoader.useBundles(new ArrayList<>(bundles));
}
}
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/PlatformBundleLoader.java b/container-core/src/main/java/com/yahoo/container/core/config/PlatformBundleLoader.java
new file mode 100644
index 00000000000..0ab89e223f6
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/container/core/config/PlatformBundleLoader.java
@@ -0,0 +1,72 @@
+package com.yahoo.container.core.config;
+
+import com.yahoo.osgi.Osgi;
+import org.osgi.framework.Bundle;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+
+/**
+ * Used to install the bundles that are added as platform bundles by the config-model.
+ *
+ * All platform bundles reside on disk, and they are never uninstalled.
+ * Platform bundles are allowed to pre-install other bundles on disk via the
+ * X-JDisc-Preinstall-Bundle manifest header.
+ *
+ * Attempts to install additional bundles, after the first call, should be a NOP.
+ *
+ * @author gjoranv
+ */
+public class PlatformBundleLoader {
+ private static final Logger log = Logger.getLogger(PlatformBundleLoader.class.getName());
+
+ private final Osgi osgi;
+ private final DiskBundleInstaller installer;
+
+ private Set<Bundle> installedBundles;
+ private boolean hasLoadedBundles = false;
+
+ public PlatformBundleLoader(Osgi osgi) {
+ this(osgi, new DiskBundleInstaller());
+ }
+
+ PlatformBundleLoader(Osgi osgi, DiskBundleInstaller installer) {
+ this.osgi = osgi;
+ this.installer = installer;
+ }
+
+ public void useBundles(List<String> bundlePaths) {
+ if (hasLoadedBundles) {
+ log.fine(() -> "Platform bundles have already been installed." +
+ "\nInstalled bundles: " + installedBundles +
+ "\nGiven files: " + bundlePaths);
+ return;
+ }
+ installedBundles = install(bundlePaths);
+ BundleStarter.startBundles(installedBundles);
+ hasLoadedBundles = true;
+ }
+
+ private Set<Bundle> install(List<String> bundlesToInstall) {
+ var allInstalled = new LinkedHashSet<Bundle>();
+ for (String bundlePath : bundlesToInstall) {
+ try {
+ allInstalled.addAll(installBundleFromDisk(bundlePath));
+ }
+ catch(Exception e) {
+ throw new RuntimeException("Could not install bundle '" + bundlePath + "'", e);
+ }
+ }
+ return allInstalled;
+ }
+
+ private List<Bundle> installBundleFromDisk(String bundlePath) {
+ log.info("Installing bundle from disk: " + bundlePath);
+ List<Bundle> bundles = installer.installBundles(bundlePath, osgi);
+ log.fine("Installed " + bundles.size() + " bundles for file " + bundlePath);
+ return bundles;
+ }
+
+}
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/testutil/HandlersConfigurerTestWrapper.java b/container-core/src/main/java/com/yahoo/container/core/config/testutil/HandlersConfigurerTestWrapper.java
index 9f49b016b68..503bf2f2db1 100644
--- a/container-core/src/main/java/com/yahoo/container/core/config/testutil/HandlersConfigurerTestWrapper.java
+++ b/container-core/src/main/java/com/yahoo/container/core/config/testutil/HandlersConfigurerTestWrapper.java
@@ -41,7 +41,8 @@ public class HandlersConfigurerTestWrapper {
private final static String testFiles[] = {
"components.cfg",
"handlers.cfg",
- "bundles.cfg",
+ "platform-bundles.cfg",
+ "application-bundles.cfg",
"string.cfg",
"int.cfg",
"renderers.cfg",
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/testutil/MockOsgiWrapper.java b/container-core/src/main/java/com/yahoo/container/core/config/testutil/MockOsgiWrapper.java
index ac0fbd71671..98c927b8efd 100644
--- a/container-core/src/main/java/com/yahoo/container/core/config/testutil/MockOsgiWrapper.java
+++ b/container-core/src/main/java/com/yahoo/container/core/config/testutil/MockOsgiWrapper.java
@@ -16,11 +16,6 @@ import static java.util.Collections.emptyList;
public class MockOsgiWrapper implements OsgiWrapper {
@Override
- public List<Bundle> getInitialBundles() {
- return emptyList();
- }
-
- @Override
public Bundle[] getBundles() {
return new Bundle[0];
}
diff --git a/container-core/src/main/java/com/yahoo/osgi/MockOsgi.java b/container-core/src/main/java/com/yahoo/osgi/MockOsgi.java
index d809c493565..6a700a65a03 100644
--- a/container-core/src/main/java/com/yahoo/osgi/MockOsgi.java
+++ b/container-core/src/main/java/com/yahoo/osgi/MockOsgi.java
@@ -12,15 +12,11 @@ import java.util.List;
/**
* @author Tony Vaagenes
+ * @author gjoranv
*/
public class MockOsgi extends NonWorkingOsgiFramework implements Osgi {
@Override
- public List<Bundle> getInitialBundles() {
- return Collections.emptyList();
- }
-
- @Override
public Bundle[] getBundles() {
return new Bundle[0];
}
diff --git a/container-core/src/main/java/com/yahoo/osgi/Osgi.java b/container-core/src/main/java/com/yahoo/osgi/Osgi.java
index 8f0acf41f30..513e7883594 100644
--- a/container-core/src/main/java/com/yahoo/osgi/Osgi.java
+++ b/container-core/src/main/java/com/yahoo/osgi/Osgi.java
@@ -9,11 +9,10 @@ import java.util.List;
/**
* @author Tony Vaagenes
+ * @author gjoranv
*/
public interface Osgi {
- List<Bundle> getInitialBundles();
-
Bundle[] getBundles();
/** Returns all bundles that have not been scheduled for uninstall. */
@@ -25,4 +24,7 @@ public interface Osgi {
void allowDuplicateBundles(Collection<Bundle> bundles);
+ default boolean hasFelixFramework() {
+ return false;
+ }
}
diff --git a/container-core/src/main/java/com/yahoo/osgi/OsgiImpl.java b/container-core/src/main/java/com/yahoo/osgi/OsgiImpl.java
index ed93d15c975..b34442d50a9 100644
--- a/container-core/src/main/java/com/yahoo/osgi/OsgiImpl.java
+++ b/container-core/src/main/java/com/yahoo/osgi/OsgiImpl.java
@@ -16,6 +16,7 @@ import java.util.logging.Logger;
/**
* @author Tony Vaagenes
* @author bratseth
+ * @author gjoranv
*/
public class OsgiImpl implements Osgi {
private static final Logger log = Logger.getLogger(OsgiImpl.class.getName());
@@ -42,11 +43,6 @@ public class OsgiImpl implements Osgi {
}
@Override
- public List<Bundle> getInitialBundles() {
- return initialBundles;
- }
-
- @Override
public Bundle[] getBundles() {
List<Bundle> bundles = jdiscOsgi.bundles();
return bundles.toArray(new Bundle[bundles.size()]);
@@ -155,6 +151,11 @@ public class OsgiImpl implements Osgi {
jdiscOsgi.allowDuplicateBundles(bundles);
}
+ @Override
+ public boolean hasFelixFramework() {
+ return jdiscOsgi.isFelixFramework();
+ }
+
private static Bundle firstNonFrameworkBundle(List<Bundle> bundles) {
for (Bundle b : bundles) {
if (! (b instanceof Framework))
diff --git a/container-core/src/test/java/com/yahoo/container/core/config/BundleManagerTest.java b/container-core/src/test/java/com/yahoo/container/core/config/ApplicationBundleLoaderTest.java
index 414e6b05128..ee71e8a8f4c 100644
--- a/container-core/src/test/java/com/yahoo/container/core/config/BundleManagerTest.java
+++ b/container-core/src/test/java/com/yahoo/container/core/config/ApplicationBundleLoaderTest.java
@@ -16,27 +16,27 @@ import static org.junit.Assert.assertTrue;
/**
* @author gjoranv
*/
-public class BundleManagerTest {
+public class ApplicationBundleLoaderTest {
private static final FileReference BUNDLE_1_REF = new FileReference("bundle-1");
private static final Bundle BUNDLE_1 = new TestBundle(BUNDLE_1_REF.value());
private static final FileReference BUNDLE_2_REF = new FileReference("bundle-2");
private static final Bundle BUNDLE_2 = new TestBundle(BUNDLE_2_REF.value());
- private BundleManager bundleLoader;
+ private ApplicationBundleLoader bundleLoader;
private TestOsgi osgi;
@Before
public void setup() {
osgi = new TestOsgi(testBundles());
var bundleInstaller = new TestBundleInstaller();
- bundleLoader = new BundleManager(osgi);
+ bundleLoader = new ApplicationBundleLoader(osgi);
bundleLoader.useCustomBundleInstaller(bundleInstaller);
}
@Test
public void bundles_are_installed_and_started() {
- bundleLoader.use(List.of(BUNDLE_1_REF));
+ bundleLoader.useBundles(List.of(BUNDLE_1_REF));
assertEquals(1, osgi.getInstalledBundles().size());
// The bundle is installed and started
@@ -51,8 +51,8 @@ public class BundleManagerTest {
@Test
public void new_bundle_can_be_installed_in_reconfig() {
- bundleLoader.use(List.of(BUNDLE_1_REF));
- Set<Bundle> obsoleteBundles = bundleLoader.use(List.of(BUNDLE_1_REF, BUNDLE_2_REF));
+ bundleLoader.useBundles(List.of(BUNDLE_1_REF));
+ Set<Bundle> obsoleteBundles = bundleLoader.useBundles(List.of(BUNDLE_1_REF, BUNDLE_2_REF));
// No bundles are obsolete
assertTrue(obsoleteBundles.isEmpty());
@@ -76,8 +76,8 @@ public class BundleManagerTest {
@Test
public void unused_bundle_is_marked_obsolete_after_reconfig() {
- bundleLoader.use(List.of(BUNDLE_1_REF));
- Set<Bundle> obsoleteBundles = bundleLoader.use(List.of(BUNDLE_2_REF));
+ bundleLoader.useBundles(List.of(BUNDLE_1_REF));
+ Set<Bundle> obsoleteBundles = bundleLoader.useBundles(List.of(BUNDLE_2_REF));
// The returned set of obsolete bundles contains bundle-1
assertEquals(1, obsoleteBundles.size());
diff --git a/container-core/src/test/java/com/yahoo/container/core/config/PlatformBundleLoaderTest.java b/container-core/src/test/java/com/yahoo/container/core/config/PlatformBundleLoaderTest.java
new file mode 100644
index 00000000000..931b0c547fd
--- /dev/null
+++ b/container-core/src/test/java/com/yahoo/container/core/config/PlatformBundleLoaderTest.java
@@ -0,0 +1,64 @@
+package com.yahoo.container.core.config;
+
+import com.yahoo.osgi.Osgi;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author gjoranv
+ */
+public class PlatformBundleLoaderTest {
+
+ private static final String BUNDLE_1_REF = "bundle-1";
+ private static final Bundle BUNDLE_1 = new TestBundle(BUNDLE_1_REF);
+ private static final String BUNDLE_2_REF = "bundle-2";
+ private static final Bundle BUNDLE_2 = new TestBundle(BUNDLE_2_REF);
+
+ private PlatformBundleLoader bundleLoader;
+ private TestOsgi osgi;
+
+ @Before
+ public void setup() {
+ osgi = new TestOsgi(testBundles());
+ bundleLoader = new PlatformBundleLoader(osgi, new TestBundleInstaller());
+ }
+
+ @Test
+ public void bundles_are_installed_and_started() {
+ bundleLoader.useBundles(List.of(BUNDLE_1_REF));
+ assertEquals(1, osgi.getInstalledBundles().size());
+
+ // The bundle is installed and started
+ TestBundle installedBundle = (TestBundle)osgi.getInstalledBundles().get(0);
+ assertEquals(BUNDLE_1.getSymbolicName(), installedBundle.getSymbolicName());
+ assertTrue(installedBundle.started);
+ }
+
+ @Test
+ public void bundles_cannot_be_added_by_later_calls() {
+ bundleLoader.useBundles(List.of(BUNDLE_1_REF));
+ bundleLoader.useBundles(List.of(BUNDLE_2_REF)); // Should be a NOP
+
+ assertEquals(1, osgi.getInstalledBundles().size());
+ assertEquals(BUNDLE_1.getSymbolicName(), osgi.getInstalledBundles().get(0).getSymbolicName());
+ }
+
+ private static Map<String, Bundle> testBundles() {
+ return Map.of(BUNDLE_1_REF, BUNDLE_1,
+ BUNDLE_2_REF, BUNDLE_2);
+ }
+
+ static class TestBundleInstaller extends DiskBundleInstaller {
+ @Override
+ public List<Bundle> installBundles(String bundlePath, Osgi osgi) {
+ return osgi.install(bundlePath);
+ }
+ }
+}
diff --git a/container-di/CMakeLists.txt b/container-di/CMakeLists.txt
index c2b033baa92..02b2b0d34d9 100644
--- a/container-di/CMakeLists.txt
+++ b/container-di/CMakeLists.txt
@@ -1,5 +1,7 @@
# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
install_config_definition(src/main/resources/configdefinitions/bundles.def container.bundles.def)
+install_config_definition(src/main/resources/configdefinitions/application-bundles.def com.yahoo.container.di.config.application-bundles.def)
+install_config_definition(src/main/resources/configdefinitions/platform-bundles.def com.yahoo.container.di.config.platform-bundles.def)
install_config_definition(src/main/resources/configdefinitions/components.def container.components.def)
install_config_definition(src/main/resources/configdefinitions/jersey-bundles.def container.di.config.jersey-bundles.def)
install_config_definition(src/main/resources/configdefinitions/jersey-injection.def container.di.config.jersey-injection.def)
diff --git a/container-di/src/main/java/com/yahoo/container/di/Container.java b/container-di/src/main/java/com/yahoo/container/di/Container.java
index ef7813ce368..af580767a17 100644
--- a/container-di/src/main/java/com/yahoo/container/di/Container.java
+++ b/container-di/src/main/java/com/yahoo/container/di/Container.java
@@ -5,15 +5,17 @@ import com.google.inject.Injector;
import com.yahoo.config.ConfigInstance;
import com.yahoo.config.ConfigurationRuntimeException;
import com.yahoo.config.subscription.ConfigInterruptedException;
-import com.yahoo.container.BundlesConfig;
import com.yahoo.container.ComponentsConfig;
import com.yahoo.container.bundle.BundleInstantiationSpecification;
import com.yahoo.container.di.ConfigRetriever.BootstrapConfigs;
+import com.yahoo.container.di.ConfigRetriever.ComponentsConfigs;
import com.yahoo.container.di.ConfigRetriever.ConfigSnapshot;
import com.yahoo.container.di.componentgraph.core.ComponentGraph;
import com.yahoo.container.di.componentgraph.core.ComponentNode;
import com.yahoo.container.di.componentgraph.core.JerseyNode;
import com.yahoo.container.di.componentgraph.core.Node;
+import com.yahoo.container.di.config.ApplicationBundlesConfig;
+import com.yahoo.container.di.config.PlatformBundlesConfig;
import com.yahoo.container.di.config.RestApiContext;
import com.yahoo.container.di.config.SubscriberFactory;
import com.yahoo.vespa.config.ConfigKey;
@@ -23,6 +25,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
@@ -40,12 +43,14 @@ public class Container {
private static final Logger log = Logger.getLogger(Container.class.getName());
private final SubscriberFactory subscriberFactory;
- private ConfigKey<BundlesConfig> bundlesConfigKey;
+ private ConfigKey<ApplicationBundlesConfig> applicationBundlesConfigKey;
+ private ConfigKey<PlatformBundlesConfig> platformBundlesConfigKey;
private ConfigKey<ComponentsConfig> componentsConfigKey;
private final ComponentDeconstructor componentDeconstructor;
private final Osgi osgi;
private final ConfigRetriever configurer;
+ private List<String> platformBundles; // Used to verify that platform bundles don't change.
private long previousConfigGeneration = -1L;
private long leastGeneration = -1L;
@@ -54,9 +59,10 @@ public class Container {
this.componentDeconstructor = componentDeconstructor;
this.osgi = osgi;
- bundlesConfigKey = new ConfigKey<>(BundlesConfig.class, configId);
+ applicationBundlesConfigKey = new ConfigKey<>(ApplicationBundlesConfig.class, configId);
+ platformBundlesConfigKey = new ConfigKey<>(PlatformBundlesConfig.class, configId);
componentsConfigKey = new ConfigKey<>(ComponentsConfig.class, configId);
- var bootstrapKeys = Set.of(bundlesConfigKey, componentsConfigKey);
+ var bootstrapKeys = Set.of(applicationBundlesConfigKey, platformBundlesConfigKey, componentsConfigKey);
this.configurer = new ConfigRetriever(bootstrapKeys, subscriberFactory::getSubscriber);
}
@@ -74,7 +80,6 @@ public class Container {
deconstructObsoleteComponents(oldGraph, newGraph, obsoleteBundles);
return newGraph;
} catch (Throwable t) {
- // TODO: Wrap ComponentConstructorException in an Error when generation==0 (+ unit test that Error is thrown)
invalidateGeneration(oldGraph.generation(), t);
throw t;
}
@@ -98,28 +103,26 @@ public class Container {
"Got bootstrap configs out of sequence for old config generation %d.\n" + "Previous config generation is %d",
getBootstrapGeneration(), previousConfigGeneration));
}
- log.log(FINE,
- String.format(
- "Got new bootstrap generation\n" + "bootstrap generation = %d\n" + "components generation: %d\n"
- + "previous generation: %d\n",
- getBootstrapGeneration(), getComponentsGeneration(), previousConfigGeneration));
+ log.log(FINE, "Got new bootstrap generation\n" + configGenerationsString());
- Collection<Bundle> bundlesToRemove = installBundles(snapshot.configs());
+ if (graph.generation() == 0) {
+ platformBundles = getConfig(platformBundlesConfigKey, snapshot.configs()).bundlePaths();
+ osgi.installPlatformBundles(platformBundles);
+ } else {
+ throwIfPlatformBundlesChanged(snapshot);
+ }
+ Collection<Bundle> bundlesToRemove = installApplicationBundles(snapshot.configs());
obsoleteBundles.addAll(bundlesToRemove);
graph = createComponentsGraph(snapshot.configs(), getBootstrapGeneration(), fallbackInjector);
// Continues loop
- } else if (snapshot instanceof ConfigRetriever.ComponentsConfigs) {
+ } else if (snapshot instanceof ComponentsConfigs) {
break;
}
}
- log.log(FINE,
- String.format(
- "Got components configs,\n" + "bootstrap generation = %d\n" + "components generation: %d\n"
- + "previous generation: %d",
- getBootstrapGeneration(), getComponentsGeneration(), previousConfigGeneration));
+ log.log(FINE, "Got components configs,\n" + configGenerationsString());
return createAndConfigureComponentsGraph(snapshot.configs(), fallbackInjector);
}
@@ -131,6 +134,17 @@ public class Container {
return configurer.getComponentsGeneration();
}
+ private String configGenerationsString() {
+ return String.format("bootstrap generation = %d\n" + "components generation: %d\n" + "previous generation: %d",
+ getBootstrapGeneration(), getComponentsGeneration(), previousConfigGeneration);
+ }
+
+ private void throwIfPlatformBundlesChanged(ConfigSnapshot snapshot) {
+ var checkPlatformBundles = getConfig(platformBundlesConfigKey, snapshot.configs()).bundlePaths();
+ if (! checkPlatformBundles.equals(platformBundles))
+ throw new RuntimeException("Platform bundles are not allowed to change!\nOld: " + platformBundles + "\nNew: " + checkPlatformBundles);
+ }
+
private ComponentGraph createAndConfigureComponentsGraph(Map<ConfigKey<? extends ConfigInstance>, ConfigInstance> componentsConfigs,
Injector fallbackInjector) {
ComponentGraph componentGraph = createComponentsGraph(componentsConfigs, getComponentsGeneration(), fallbackInjector);
@@ -151,9 +165,9 @@ public class Container {
componentDeconstructor.deconstruct(oldComponents.keySet(), obsoleteBundles);
}
- private Set<Bundle> installBundles(Map<ConfigKey<? extends ConfigInstance>, ConfigInstance> configsIncludingBootstrapConfigs) {
- BundlesConfig bundlesConfig = getConfig(bundlesConfigKey, configsIncludingBootstrapConfigs);
- return osgi.useBundles(bundlesConfig.bundle());
+ private Set<Bundle> installApplicationBundles(Map<ConfigKey<? extends ConfigInstance>, ConfigInstance> configsIncludingBootstrapConfigs) {
+ ApplicationBundlesConfig applicationBundlesConfig = getConfig(applicationBundlesConfigKey, configsIncludingBootstrapConfigs);
+ return osgi.useApplicationBundles(applicationBundlesConfig.bundles());
}
private ComponentGraph createComponentsGraph(Map<ConfigKey<? extends ConfigInstance>, ConfigInstance> configsIncludingBootstrapConfigs,
diff --git a/container-di/src/main/java/com/yahoo/container/di/Osgi.java b/container-di/src/main/java/com/yahoo/container/di/Osgi.java
index ab7da7665b6..940986e2f38 100644
--- a/container-di/src/main/java/com/yahoo/container/di/Osgi.java
+++ b/container-di/src/main/java/com/yahoo/container/di/Osgi.java
@@ -25,11 +25,15 @@ public interface Osgi {
return new BundleClasses(new MockBundle(), Collections.emptySet());
}
+ default void installPlatformBundles(Collection<String> bundlePaths) {
+ System.out.println("installPlatformBundles " + bundlePaths);
+ }
+
/**
* Returns the set of bundles that is not used by the current application generation,
* and therefore should be scheduled for uninstalling.
*/
- default Set<Bundle> useBundles(Collection<FileReference> bundles) {
+ default Set<Bundle> useApplicationBundles(Collection<FileReference> bundles) {
System.out.println("useBundles " + bundles.stream().map(Object::toString).collect(Collectors.joining(", ")));
return emptySet();
}
diff --git a/container-di/src/main/resources/configdefinitions/application-bundles.def b/container-di/src/main/resources/configdefinitions/application-bundles.def
new file mode 100644
index 00000000000..7e03b1e3ac8
--- /dev/null
+++ b/container-di/src/main/resources/configdefinitions/application-bundles.def
@@ -0,0 +1,5 @@
+# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package=com.yahoo.container.di.config
+
+# References to user bundles to install.
+bundles[] file
diff --git a/container-di/src/main/resources/configdefinitions/bundles.def b/container-di/src/main/resources/configdefinitions/bundles.def
index 9e10d863106..79e24742398 100644
--- a/container-di/src/main/resources/configdefinitions/bundles.def
+++ b/container-di/src/main/resources/configdefinitions/bundles.def
@@ -1,5 +1,5 @@
# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
namespace=container
-# References to all 3rd-party bundles to be installed.
+# References to both application and platform bundles to install.
bundle[] file
diff --git a/container-di/src/main/resources/configdefinitions/platform-bundles.def b/container-di/src/main/resources/configdefinitions/platform-bundles.def
new file mode 100644
index 00000000000..a30a846b565
--- /dev/null
+++ b/container-di/src/main/resources/configdefinitions/platform-bundles.def
@@ -0,0 +1,5 @@
+# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package=com.yahoo.container.di.config
+
+# Paths to platform bundles to install.
+bundlePaths[] string
diff --git a/container-di/src/test/java/com/yahoo/container/di/ContainerTest.java b/container-di/src/test/java/com/yahoo/container/di/ContainerTest.java
index eac64c20274..19f277ff8fb 100644
--- a/container-di/src/test/java/com/yahoo/container/di/ContainerTest.java
+++ b/container-di/src/test/java/com/yahoo/container/di/ContainerTest.java
@@ -267,7 +267,8 @@ public class ContainerTest extends ContainerTestBase {
"inject[0].forClass \"" + injectedClass.getName() + "\"\n";
dirConfigSource.writeConfig("components", componentsConfig);
- dirConfigSource.writeConfig("bundles", "");
+ dirConfigSource.writeConfig("platform-bundles", "");
+ dirConfigSource.writeConfig("application-bundles", "");
dirConfigSource.writeConfig("jersey-bundles", "bundles[0].spec \"mock-entry-to-enforce-a-MockBundle\"");
dirConfigSource.writeConfig("jersey-injection", injectionConfig);
diff --git a/container-di/src/test/java/com/yahoo/container/di/ContainerTestBase.java b/container-di/src/test/java/com/yahoo/container/di/ContainerTestBase.java
index 18236a6bde9..f1f3c4a2ae4 100644
--- a/container-di/src/test/java/com/yahoo/container/di/ContainerTestBase.java
+++ b/container-di/src/test/java/com/yahoo/container/di/ContainerTestBase.java
@@ -61,7 +61,7 @@ public class ContainerTestBase {
}
@Override
- public Set<Bundle> useBundles(Collection<FileReference> bundles) {
+ public Set<Bundle> useApplicationBundles(Collection<FileReference> bundles) {
return emptySet();
}
@@ -81,7 +81,8 @@ public class ContainerTestBase {
}
protected void writeBootstrapConfigs(ComponentEntry... componentEntries) {
- dirConfigSource.writeConfig("bundles", "");
+ dirConfigSource.writeConfig("platform-bundles", "");
+ dirConfigSource.writeConfig("application-bundles", "");
StringBuilder components = new StringBuilder();
for (int i = 0; i < componentEntries.length; i++) {
components.append(componentEntries[i].asConfig(i));
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/application/OsgiFramework.java b/jdisc_core/src/main/java/com/yahoo/jdisc/application/OsgiFramework.java
index b1aceb81bc6..12168663205 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/application/OsgiFramework.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/application/OsgiFramework.java
@@ -113,4 +113,11 @@ public interface OsgiFramework {
*/
void stop() throws BundleException;
+ /**
+ * Returns true if this is a Felix based framework and not e.g. a test framework.
+ */
+ default boolean isFelixFramework() {
+ return false;
+ }
+
}
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/core/FelixFramework.java b/jdisc_core/src/main/java/com/yahoo/jdisc/core/FelixFramework.java
index c14e513fb98..bd189f8b898 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/core/FelixFramework.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/core/FelixFramework.java
@@ -169,6 +169,11 @@ public class FelixFramework implements OsgiFramework {
collisionHook.allowDuplicateBundles(bundles);
}
+ @Override
+ public boolean isFelixFramework() {
+ return true;
+ }
+
private void installBundle(String bundleLocation, Set<String> mask, List<Bundle> out) throws BundleException {
bundleLocation = BundleLocationResolver.resolve(bundleLocation);
if (mask.contains(bundleLocation)) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java
index 3f8cc58540d..2afbb0e6476 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java
@@ -3,7 +3,6 @@ package com.yahoo.vespa.hosted.provision.maintenance;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.Zone;
import com.yahoo.jdisc.Metric;
@@ -23,24 +22,21 @@ import java.util.stream.Collectors;
/**
* This moves expired failed nodes:
- * <ul>
- * <li>To parked: If the node has known hardware failure, Docker hosts are moved to parked only when all of their
- * children are already in parked
- * <li>To dirty: If the node has failed less than 5 times OR the environment is dev, test or perf.
- * Those environments have no protection against users running bogus applications, so
- * we cannot use the node failure count to conclude the node has a failure.
- * <li>Otherwise the node will remain in failed
- * </ul>
+ *
+ * - To parked: If the node has known hardware failure, Docker hosts are moved to parked only when all of their
+ * children are already in parked.
+ * - To dirty: If the node is a host and has failed less than 5 times, or always if the node is a child.
+ * - Otherwise the node will remain in failed.
+ *
* Failed content nodes are given a long expiry time to enable us to manually moved them back to
* active to recover data in cases where the node was failed accidentally.
- * <p>
+ *
* Failed container (Vespa, not Docker) nodes are expired early as there's no data to potentially recover.
- * </p>
- * <p>
+ *
* The purpose of the automatic recycling to dirty + fail count is that nodes which were moved
* to failed due to some undetected hardware failure will end up being failed again.
* When that has happened enough they will not be recycled.
- * <p>
+ *
* Nodes with detected hardware issues will not be recycled.
*
* @author bratseth
@@ -125,8 +121,7 @@ public class FailedExpirer extends NodeRepositoryMaintainer {
/** Returns whether the current node fail count should be used as an indicator of hardware issue */
private boolean failCountIndicatesHardwareIssue(Node node) {
- if (node.flavor().getType() == Flavor.Type.DOCKER_CONTAINER) return false;
- return (zone.environment() == Environment.prod || zone.environment() == Environment.staging) &&
- node.status().failCount() >= maxAllowedFailures;
+ return node.type().isHost() && node.status().failCount() >= maxAllowedFailures;
}
+
}
diff --git a/searchlib/src/tests/common/location/geo_location_test.cpp b/searchlib/src/tests/common/location/geo_location_test.cpp
index 8093ea61697..31b844d0fc8 100644
--- a/searchlib/src/tests/common/location/geo_location_test.cpp
+++ b/searchlib/src/tests/common/location/geo_location_test.cpp
@@ -9,6 +9,15 @@
using search::common::GeoLocation;
using search::common::GeoLocationParser;
+using Box = search::common::GeoLocation::Box;
+using Point = search::common::GeoLocation::Point;
+using Range = search::common::GeoLocation::Range;
+using Aspect = search::common::GeoLocation::Aspect;
+
+constexpr int32_t plus_inf = std::numeric_limits<int32_t>::max();
+constexpr int32_t minus_inf = std::numeric_limits<int32_t>::min();
+constexpr uint32_t u32_inf = std::numeric_limits<uint32_t>::max();
+
bool is_parseable(const char *str) {
GeoLocationParser parser;
return parser.parseOldFormat(str);
@@ -20,7 +29,7 @@ GeoLocation parse(const char *str) {
return parser.getGeoLocation();
}
-TEST(GeoLocationTest, malformed_bounding_boxes_are_not_parseable) {
+TEST(GeoLocationParserTest, malformed_bounding_boxes_are_not_parseable) {
EXPECT_TRUE(is_parseable("[2,10,20,30,40]"));
EXPECT_FALSE(is_parseable("[2,10,20,30,40][2,10,20,30,40]"));
EXPECT_FALSE(is_parseable("[1,10,20,30,40]"));
@@ -31,7 +40,7 @@ TEST(GeoLocationTest, malformed_bounding_boxes_are_not_parseable) {
EXPECT_FALSE(is_parseable("[10,20,30,40]"));
}
-TEST(GeoLocationTest, malformed_circles_are_not_parseable) {
+TEST(GeoLocationParserTest, malformed_circles_are_not_parseable) {
EXPECT_TRUE(is_parseable("(2,10,20,5,0,0,0)"));
EXPECT_FALSE(is_parseable("(2,10,20,5,0,0,0)(2,10,20,5,0,0,0)"));
EXPECT_FALSE(is_parseable("(1,10,20,5,0,0,0)"));
@@ -43,7 +52,7 @@ TEST(GeoLocationTest, malformed_circles_are_not_parseable) {
EXPECT_FALSE(is_parseable("(10,20,5)"));
}
-TEST(GeoLocationTest, bounding_boxes_can_be_parsed) {
+TEST(GeoLocationParserTest, bounding_boxes_can_be_parsed) {
auto loc = parse("[2,10,20,30,40]");
EXPECT_EQ(false, loc.has_point);
EXPECT_EQ(true, loc.bounding_box.active());
@@ -57,7 +66,7 @@ TEST(GeoLocationTest, bounding_boxes_can_be_parsed) {
EXPECT_EQ(40, loc.bounding_box.y.high);
}
-TEST(GeoLocationTest, circles_can_be_parsed) {
+TEST(GeoLocationParserTest, circles_can_be_parsed) {
auto loc = parse("(2,10,20,5,0,0,0)");
EXPECT_EQ(true, loc.has_point);
EXPECT_EQ(true, loc.bounding_box.active());
@@ -71,7 +80,7 @@ TEST(GeoLocationTest, circles_can_be_parsed) {
EXPECT_EQ(25, loc.bounding_box.y.high);
}
-TEST(GeoLocationTest, circles_can_have_aspect_ratio) {
+TEST(GeoLocationParserTest, circles_can_have_aspect_ratio) {
auto loc = parse("(2,10,20,5,0,0,0,2147483648)");
EXPECT_EQ(true, loc.has_point);
EXPECT_EQ(true, loc.bounding_box.active());
@@ -85,7 +94,7 @@ TEST(GeoLocationTest, circles_can_have_aspect_ratio) {
EXPECT_EQ(25, loc.bounding_box.y.high);
}
-TEST(GeoLocationTest, bounding_box_can_be_specified_after_circle) {
+TEST(GeoLocationParserTest, bounding_box_can_be_specified_after_circle) {
auto loc = parse("(2,10,20,5,0,0,0)[2,10,20,30,40]");
EXPECT_EQ(true, loc.has_point);
EXPECT_EQ(true, loc.bounding_box.active());
@@ -99,7 +108,7 @@ TEST(GeoLocationTest, bounding_box_can_be_specified_after_circle) {
EXPECT_EQ(25, loc.bounding_box.y.high);
}
-TEST(GeoLocationTest, circles_can_be_specified_after_bounding_box) {
+TEST(GeoLocationParserTest, circles_can_be_specified_after_bounding_box) {
auto loc = parse("[2,10,20,30,40](2,10,20,5,0,0,0)");
EXPECT_EQ(true, loc.has_point);
EXPECT_EQ(true, loc.bounding_box.active());
@@ -113,13 +122,13 @@ TEST(GeoLocationTest, circles_can_be_specified_after_bounding_box) {
EXPECT_EQ(25, loc.bounding_box.y.high);
}
-TEST(GeoLocationTest, santa_search_gives_non_wrapped_bounding_box) {
+TEST(GeoLocationParserTest, santa_search_gives_non_wrapped_bounding_box) {
auto loc = parse("(2,122163600,89998536,290112,4,2000,0,109704)");
EXPECT_GE(loc.bounding_box.x.high, loc.bounding_box.x.low);
EXPECT_GE(loc.bounding_box.y.high, loc.bounding_box.y.low);
}
-TEST(GeoLocationTest, near_boundary_search_gives_non_wrapped_bounding_box) {
+TEST(GeoLocationParserTest, near_boundary_search_gives_non_wrapped_bounding_box) {
auto loc1 = parse("(2,2000000000,2000000000,3000000000,0,1,0)");
EXPECT_GE(loc1.bounding_box.x.high, loc1.bounding_box.x.low);
EXPECT_GE(loc1.bounding_box.y.high, loc1.bounding_box.y.low);
@@ -133,4 +142,253 @@ TEST(GeoLocationTest, near_boundary_search_gives_non_wrapped_bounding_box) {
EXPECT_EQ(std::numeric_limits<int32_t>::min(), loc2.bounding_box.y.low);
}
+void check_box(const GeoLocation &location, Box expected)
+{
+ int32_t lx = expected.x.low;
+ int32_t hx = expected.x.high;
+ int32_t ly = expected.y.low;
+ int32_t hy = expected.y.high;
+ EXPECT_TRUE(location.inside_limit(Point{lx,ly}));
+ EXPECT_TRUE(location.inside_limit(Point{lx,hy}));
+ EXPECT_TRUE(location.inside_limit(Point{hx,ly}));
+ EXPECT_TRUE(location.inside_limit(Point{hx,hy}));
+
+ EXPECT_FALSE(location.inside_limit(Point{lx,ly-1}));
+ EXPECT_FALSE(location.inside_limit(Point{lx,hy+1}));
+ EXPECT_FALSE(location.inside_limit(Point{lx-1,ly}));
+ EXPECT_FALSE(location.inside_limit(Point{lx-1,hy}));
+ EXPECT_FALSE(location.inside_limit(Point{hx,ly-1}));
+ EXPECT_FALSE(location.inside_limit(Point{hx,hy+1}));
+ EXPECT_FALSE(location.inside_limit(Point{hx+1,ly}));
+ EXPECT_FALSE(location.inside_limit(Point{hx+1,hy}));
+
+ EXPECT_FALSE(location.inside_limit(Point{plus_inf,plus_inf}));
+ EXPECT_FALSE(location.inside_limit(Point{minus_inf,minus_inf}));
+}
+
+TEST(GeoLocationTest, invalid_location) {
+ GeoLocation invalid;
+ EXPECT_FALSE(invalid.valid());
+ EXPECT_FALSE(invalid.has_radius());
+ EXPECT_FALSE(invalid.can_limit());
+ EXPECT_FALSE(invalid.has_point);
+ EXPECT_FALSE(invalid.bounding_box.active());
+ EXPECT_FALSE(invalid.x_aspect.active());
+
+ EXPECT_EQ(invalid.sq_distance_to(Point{0,0}), 0);
+ EXPECT_EQ(invalid.sq_distance_to(Point{999999,999999}), 0);
+ EXPECT_EQ(invalid.sq_distance_to(Point{-999999,-999999}), 0);
+ EXPECT_EQ(invalid.sq_distance_to(Point{plus_inf,plus_inf}), 0);
+ EXPECT_EQ(invalid.sq_distance_to(Point{minus_inf,minus_inf}), 0);
+
+ EXPECT_TRUE(invalid.inside_limit(Point{0,0}));
+ EXPECT_TRUE(invalid.inside_limit(Point{999999,999999}));
+ EXPECT_TRUE(invalid.inside_limit(Point{-999999,-999999}));
+ EXPECT_TRUE(invalid.inside_limit(Point{plus_inf,plus_inf}));
+ EXPECT_TRUE(invalid.inside_limit(Point{minus_inf,minus_inf}));
+}
+
+TEST(GeoLocationTest, point_location) {
+ GeoLocation location(Point{300,-400});
+ EXPECT_TRUE(location.valid());
+ EXPECT_FALSE(location.has_radius());
+ EXPECT_FALSE(location.can_limit());
+ EXPECT_TRUE(location.has_point);
+ EXPECT_FALSE(location.bounding_box.active());
+ EXPECT_FALSE(location.x_aspect.active());
+
+ EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500);
+ EXPECT_EQ(location.sq_distance_to(Point{300,-400}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{300,400}), 640000);
+
+ EXPECT_TRUE(location.inside_limit(Point{0,0}));
+ EXPECT_TRUE(location.inside_limit(Point{999999,999999}));
+ EXPECT_TRUE(location.inside_limit(Point{-999999,-999999}));
+ EXPECT_TRUE(location.inside_limit(Point{plus_inf,plus_inf}));
+ EXPECT_TRUE(location.inside_limit(Point{minus_inf,minus_inf}));
+}
+
+TEST(GeoLocationTest, point_and_radius) {
+ GeoLocation location(Point{300,-400}, 500);
+ EXPECT_TRUE(location.valid());
+ EXPECT_TRUE(location.has_radius());
+ EXPECT_TRUE(location.can_limit());
+ EXPECT_TRUE(location.has_point);
+ EXPECT_TRUE(location.bounding_box.active());
+ EXPECT_FALSE(location.x_aspect.active());
+
+ EXPECT_EQ(location.radius, 500);
+
+ EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500);
+ EXPECT_EQ(location.sq_distance_to(Point{300,-400}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{300,400}), 640000);
+
+ EXPECT_TRUE(location.inside_limit(Point{0,0}));
+ EXPECT_TRUE(location.inside_limit(Point{-200,-400}));
+ EXPECT_TRUE(location.inside_limit(Point{800,-400}));
+ EXPECT_TRUE(location.inside_limit(Point{300,-400}));
+ EXPECT_TRUE(location.inside_limit(Point{300,100}));
+ EXPECT_TRUE(location.inside_limit(Point{300,-900}));
+
+ check_box(location, Box{Range{0,600},{-800,0}});
+}
+
+TEST(GeoLocationTest, point_and_aspect) {
+ GeoLocation location(Point{600,400}, Aspect{0.5});
+ // same: GeoLocation location(Point{600,400}, Aspect{1ul << 31});
+ EXPECT_TRUE(location.valid());
+ EXPECT_FALSE(location.has_radius());
+ EXPECT_FALSE(location.can_limit());
+ EXPECT_TRUE(location.has_point);
+ EXPECT_FALSE(location.bounding_box.active());
+ EXPECT_TRUE(location.x_aspect.active());
+ EXPECT_EQ(location.x_aspect.multiplier, 1ul << 31);
+
+ EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500);
+ EXPECT_EQ(location.sq_distance_to(Point{600,400}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{1200,800}), 500*500);
+
+ EXPECT_TRUE(location.inside_limit(Point{0,0}));
+ EXPECT_TRUE(location.inside_limit(Point{999999,999999}));
+ EXPECT_TRUE(location.inside_limit(Point{-999999,-999999}));
+ EXPECT_TRUE(location.inside_limit(Point{plus_inf,plus_inf}));
+ EXPECT_TRUE(location.inside_limit(Point{minus_inf,minus_inf}));
+}
+
+TEST(GeoLocationTest, point_radius_and_aspect) {
+ GeoLocation location(Point{1200,400}, 500, Aspect{0.25});
+ EXPECT_TRUE(location.valid());
+ EXPECT_TRUE(location.has_radius());
+ EXPECT_TRUE(location.can_limit());
+ EXPECT_TRUE(location.has_point);
+ EXPECT_TRUE(location.bounding_box.active());
+ EXPECT_TRUE(location.x_aspect.active());
+ EXPECT_EQ(location.x_aspect.multiplier, 1ul << 30);
+
+ EXPECT_EQ(location.radius, 500);
+
+ EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500);
+ EXPECT_EQ(location.sq_distance_to(Point{1200,400}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{1240,400}), 100);
+
+ EXPECT_TRUE(location.inside_limit(Point{1200,400}));
+ EXPECT_TRUE(location.inside_limit(Point{0,0}));
+ EXPECT_TRUE(location.inside_limit(Point{2400,0}));
+ EXPECT_TRUE(location.inside_limit(Point{2400,800}));
+ EXPECT_TRUE(location.inside_limit(Point{0,800}));
+ // note: must be 4 outside since 3*0.25 may be truncated to 0
+ EXPECT_FALSE(location.inside_limit(Point{-4,0}));
+ EXPECT_FALSE(location.inside_limit(Point{-4,800}));
+ EXPECT_FALSE(location.inside_limit(Point{2404,0}));
+ EXPECT_FALSE(location.inside_limit(Point{2404,800}));
+ EXPECT_FALSE(location.inside_limit(Point{2400,-1}));
+ EXPECT_FALSE(location.inside_limit(Point{2400,801}));
+ EXPECT_FALSE(location.inside_limit(Point{0,-1}));
+ EXPECT_FALSE(location.inside_limit(Point{0,801}));
+ EXPECT_FALSE(location.inside_limit(Point{plus_inf,plus_inf}));
+ EXPECT_FALSE(location.inside_limit(Point{minus_inf,minus_inf}));
+}
+
+TEST(GeoLocationTest, box_location) {
+ Box mybox{Range{300,350},Range{400,450}};
+ GeoLocation location(mybox);
+ EXPECT_TRUE(location.valid());
+ EXPECT_FALSE(location.has_radius());
+ EXPECT_TRUE(location.can_limit());
+ EXPECT_FALSE(location.has_point);
+ EXPECT_TRUE(location.bounding_box.active());
+ EXPECT_FALSE(location.x_aspect.active());
+
+ // currently does not measure distance outside box:
+ EXPECT_EQ(location.sq_distance_to(Point{0,0}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{300,400}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{350,450}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{450,550}), 0);
+
+ EXPECT_TRUE(location.inside_limit(Point{333,444}));
+ EXPECT_FALSE(location.inside_limit(Point{0,0}));
+ check_box(location, mybox);
+}
+
+TEST(GeoLocationTest, box_and_point) {
+ Box mybox{Range{287,343},Range{366,401}};
+ GeoLocation location(mybox, Point{300,400});
+ EXPECT_TRUE(location.valid());
+ EXPECT_FALSE(location.has_radius());
+ EXPECT_TRUE(location.can_limit());
+ EXPECT_TRUE(location.has_point);
+ EXPECT_TRUE(location.bounding_box.active());
+ EXPECT_FALSE(location.x_aspect.active());
+
+ EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500);
+ EXPECT_EQ(location.sq_distance_to(Point{300,400}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{300,423}), 23*23);
+
+ check_box(location, mybox);
+}
+
+TEST(GeoLocationTest, box_point_and_aspect) {
+ Box mybox{Range{-1000,350},Range{-1000,600}};
+ GeoLocation location(mybox, Point{600,400}, Aspect{0.5});
+ EXPECT_TRUE(location.valid());
+ EXPECT_FALSE(location.has_radius());
+ EXPECT_TRUE(location.can_limit());
+ EXPECT_TRUE(location.has_point);
+ EXPECT_TRUE(location.bounding_box.active());
+ EXPECT_TRUE(location.x_aspect.active());
+
+ EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500);
+ EXPECT_EQ(location.sq_distance_to(Point{600,400}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{600,407}), 7*7);
+ EXPECT_EQ(location.sq_distance_to(Point{614,400}), 7*7);
+
+ check_box(location, mybox);
+}
+
+TEST(GeoLocationTest, box_point_and_radius) {
+ Box mybox{Range{-1000,350},Range{-1000,600}};
+ GeoLocation location(mybox, Point{300,400}, 500);
+ EXPECT_TRUE(location.valid());
+ EXPECT_TRUE(location.has_radius());
+ EXPECT_TRUE(location.can_limit());
+ EXPECT_TRUE(location.has_point);
+ EXPECT_TRUE(location.bounding_box.active());
+ EXPECT_FALSE(location.x_aspect.active());
+
+ EXPECT_EQ(location.radius, 500);
+
+ EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500);
+ EXPECT_EQ(location.sq_distance_to(Point{300,400}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{300,423}), 23*23);
+
+ EXPECT_EQ(location.bounding_box.x.low, -200);
+ EXPECT_EQ(location.bounding_box.y.low, -100);
+ EXPECT_EQ(location.bounding_box.x.high, 350);
+ EXPECT_EQ(location.bounding_box.y.high, 600);
+}
+
+TEST(GeoLocationTest, box_point_radius_and_aspect) {
+ Box mybox{Range{-1000,650},Range{-1000,700}};
+ GeoLocation location(mybox, Point{600,400}, 500, Aspect{0.5});
+ EXPECT_TRUE(location.valid());
+ EXPECT_TRUE(location.has_radius());
+ EXPECT_TRUE(location.can_limit());
+ EXPECT_TRUE(location.has_point);
+ EXPECT_TRUE(location.bounding_box.active());
+ EXPECT_TRUE(location.x_aspect.active());
+
+ EXPECT_EQ(location.radius, 500);
+
+ EXPECT_EQ(location.sq_distance_to(Point{0,0}), 500*500);
+ EXPECT_EQ(location.sq_distance_to(Point{600,400}), 0);
+ EXPECT_EQ(location.sq_distance_to(Point{600,407}), 7*7);
+ EXPECT_EQ(location.sq_distance_to(Point{614,400}), 7*7);
+
+ EXPECT_GE(location.bounding_box.x.low, -402);
+ EXPECT_LE(location.bounding_box.x.low, -400);
+ EXPECT_EQ(location.bounding_box.y.low, -100);
+ EXPECT_EQ(location.bounding_box.x.high, 650);
+ EXPECT_EQ(location.bounding_box.y.high, 700);
+}
+
GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/searchlib/src/vespa/searchlib/common/geo_location.h b/searchlib/src/vespa/searchlib/common/geo_location.h
index 5d04a09142a..261951caf3e 100644
--- a/searchlib/src/vespa/searchlib/common/geo_location.h
+++ b/searchlib/src/vespa/searchlib/common/geo_location.h
@@ -28,6 +28,8 @@ struct GeoLocation
uint32_t multiplier;
Aspect() : multiplier(0) {}
Aspect(uint32_t multiplier_in) : multiplier(multiplier_in) {}
+ // for unit tests:
+ Aspect(double multiplier_in) : multiplier(multiplier_in*4294967296.0) {}
bool active() const { return multiplier != 0; }
};
struct Range {
diff --git a/standalone-container/src/test/java/com/yahoo/container/standalone/StandaloneSubscriberTest.java b/standalone-container/src/test/java/com/yahoo/container/standalone/StandaloneSubscriberTest.java
index dd755e8e6dd..8ca20fdf7cd 100644
--- a/standalone-container/src/test/java/com/yahoo/container/standalone/StandaloneSubscriberTest.java
+++ b/standalone-container/src/test/java/com/yahoo/container/standalone/StandaloneSubscriberTest.java
@@ -2,8 +2,9 @@
package com.yahoo.container.standalone;
import com.yahoo.config.ConfigInstance;
-import com.yahoo.container.BundlesConfig;
import com.yahoo.container.ComponentsConfig;
+import com.yahoo.container.di.config.ApplicationBundlesConfig;
+import com.yahoo.container.di.config.PlatformBundlesConfig;
import com.yahoo.container.di.config.Subscriber;
import com.yahoo.vespa.config.ConfigKey;
import org.junit.Ignore;
@@ -23,7 +24,8 @@ import static org.junit.Assert.assertThat;
* @author ollivir
*/
public class StandaloneSubscriberTest {
- private static ConfigKey<ConfigInstance> bundlesKey = key("bundles");
+ private static ConfigKey<ConfigInstance> platformBundlesKey = key("platform-bundles");
+ private static ConfigKey<ConfigInstance> applicationBundlesKey = key("application-bundles");
private static ConfigKey<ConfigInstance> componentsKey = key("components");
private static ConfigKey<ConfigInstance> key(String name) {
@@ -35,16 +37,19 @@ public class StandaloneSubscriberTest {
public void standalone_subscriber() throws Exception {
withContainerModel("<container version=\"1.0\"></container>", root -> {
Set<ConfigKey<ConfigInstance>> keys = new HashSet<>();
- keys.add(bundlesKey);
+ keys.add(platformBundlesKey);
+ keys.add(applicationBundlesKey);
keys.add(componentsKey);
Subscriber subscriber = new StandaloneSubscriberFactory(root).getSubscriber(keys);
Map<ConfigKey<ConfigInstance>, ConfigInstance> config = subscriber.config();
assertThat(config.size(), is(2));
- BundlesConfig bundlesConfig = (BundlesConfig) config.get(bundlesKey);
+ PlatformBundlesConfig platformBundlesConfig = (PlatformBundlesConfig) config.get(platformBundlesKey);
+ ApplicationBundlesConfig applicationBundlesConfig = (ApplicationBundlesConfig) config.get(applicationBundlesKey);
ComponentsConfig componentsConfig = (ComponentsConfig) config.get(componentsKey);
- assertThat(bundlesConfig.bundle().size(), is(0));
+ assertThat(platformBundlesConfig.bundlePaths().size(), is(0));
+ assertThat(applicationBundlesConfig.bundles().size(), is(0));
assertThat(componentsConfig.components().size(), greaterThan(10));
return null;
});
diff --git a/vespajlib/src/main/java/com/yahoo/collections/PredicateSplit.java b/vespajlib/src/main/java/com/yahoo/collections/PredicateSplit.java
deleted file mode 100644
index 1b3941df7bf..00000000000
--- a/vespajlib/src/main/java/com/yahoo/collections/PredicateSplit.java
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.collections;
-
-import java.util.List;
-import java.util.ArrayList;
-import java.util.function.Predicate;
-
-/**
- * Class holding the result of a partition-by-predicate operation.
- **/
-public class PredicateSplit<E> {
- public final List<E> falseValues; /// list of values where the predicate returned false
- public final List<E> trueValues; /// list of values where the predicate returned true
-
- private PredicateSplit() {
- falseValues = new ArrayList<E>();
- trueValues = new ArrayList<E>();
- }
-
- /**
- * Perform a partition-by-predicate operation.
- * Each value in the input is tested by the predicate and
- * added to either the falseValues list or the trueValues list.
- * @param collection The input collection.
- * @param predicate A test for selecting the target list.
- * @return Two lists bundled in an object.
- **/
- public static <V> PredicateSplit<V> partition(Iterable<V> collection, Predicate<? super V> predicate)
- {
- PredicateSplit<V> r = new PredicateSplit<V>();
- for (V value : collection) {
- if (predicate.test(value)) {
- r.trueValues.add(value);
- } else {
- r.falseValues.add(value);
- }
- }
- return r;
- }
-}
diff --git a/vespajlib/src/test/java/com/yahoo/collections/PredicateSplitTestCase.java b/vespajlib/src/test/java/com/yahoo/collections/PredicateSplitTestCase.java
deleted file mode 100644
index 05a719d6a4d..00000000000
--- a/vespajlib/src/test/java/com/yahoo/collections/PredicateSplitTestCase.java
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.collections;
-
-import org.junit.Test;
-
-import java.util.List;
-import java.util.ArrayList;
-
-import static org.junit.Assert.assertEquals;
-
-public class PredicateSplitTestCase {
- @Test
- public void requireThatSplitWorks() {
- List<Integer> l = new ArrayList<Integer>();
- l.add(1);
- l.add(6);
- l.add(2);
- l.add(4);
- l.add(5);
- PredicateSplit<Integer> result = PredicateSplit.partition(l, x -> (x % 2 == 0));
- assertEquals((long) result.falseValues.size(), 2L);
- assertEquals((long) result.falseValues.get(0), 1L);
- assertEquals((long) result.falseValues.get(1), 5L);
-
- assertEquals((long) result.trueValues.size(), 3L);
- assertEquals((long) result.trueValues.get(0), 6L);
- assertEquals((long) result.trueValues.get(1), 2L);
- assertEquals((long) result.trueValues.get(2), 4L);
- }
-}