You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by jb...@apache.org on 2022/10/05 09:02:12 UTC
[karaf] branch main updated: [KARAF-6110] - add option to shutdown Karaf if features or repositories fail to boot
This is an automated email from the ASF dual-hosted git repository.
jbonofre pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/karaf.git
The following commit(s) were added to refs/heads/main by this push:
new 90eb36e5da [KARAF-6110] - add option to shutdown Karaf if features or repositories fail to boot
new 2fda775fb6 Merge pull request #1629 from awrb/KARAF-6110
90eb36e5da is described below
commit 90eb36e5dad705d0dd1da2024e9465cceb393982
Author: Aleksy Wróblewski <al...@bbbit.io>
AuthorDate: Fri Sep 30 08:30:37 2022 +0200
[KARAF-6110] - add option to shutdown Karaf if features or repositories fail to boot
---
.../main/resources/resources/etc/system.properties | 6 ++
.../main/resources/resources/etc/system.properties | 6 ++
.../karaf/features/internal/osgi/Activator.java | 3 +-
.../internal/service/BootFeaturesInstaller.java | 45 +++++++++---
.../karaf/features/internal/util/ExitManager.java | 24 +++++++
.../features/internal/util/SystemExitManager.java | 27 ++++++++
.../service/BootFeaturesInstallerTest.java | 81 ++++++++++++++++++----
.../karaf/instance/resources/etc/system.properties | 6 ++
8 files changed, 175 insertions(+), 23 deletions(-)
diff --git a/assemblies/features/base/src/main/resources/resources/etc/system.properties b/assemblies/features/base/src/main/resources/resources/etc/system.properties
index 24aae4bbe2..20e545008b 100644
--- a/assemblies/features/base/src/main/resources/resources/etc/system.properties
+++ b/assemblies/features/base/src/main/resources/resources/etc/system.properties
@@ -121,6 +121,12 @@ org.apache.aries.proxy.weaving.disabled = org.objectweb.asm.*,org.slf4j.*,org.ap
#
karaf.secured.services = (&(osgi.command.scope=*)(osgi.command.function=*))
+#
+# If set to true, Karaf will exit if either the resolving of feature repositories, or
+# the installation of boot features (as configured in org.apache.karaf.features.cfg) fails.
+#
+karaf.require.successful.features.boot = false
+
#
# By default, if there's no ACL policy for a certain karaf command, this command is allowed to access
# without the RBAC. We can change this behavior by enable the following property, which means
diff --git a/assemblies/features/static/src/main/resources/resources/etc/system.properties b/assemblies/features/static/src/main/resources/resources/etc/system.properties
index 9ac615526f..61ddc9ba95 100644
--- a/assemblies/features/static/src/main/resources/resources/etc/system.properties
+++ b/assemblies/features/static/src/main/resources/resources/etc/system.properties
@@ -119,6 +119,12 @@ org.apache.aries.proxy.weaving.disabled = org.objectweb.asm.*,org.slf4j.*,org.ap
#
karaf.secured.services = (&(osgi.command.scope=*)(osgi.command.function=*))
+#
+# If set to true, Karaf will exit if either the resolving of feature repositories, or
+# the installation of boot features (as configured in org.apache.karaf.features.cfg) fails.
+#
+karaf.require.successful.features.boot = false
+
#
# By default, if there's no ACL policy for a certain karaf command, this command is allowed to access
# without the RBAC. We can change this behavior by enable the following property, which means
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java b/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
index 3ebd56ea2c..a2d3651cc3 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
@@ -53,6 +53,7 @@ import org.apache.karaf.features.internal.service.FeaturesServiceImpl;
import org.apache.karaf.features.internal.service.BundleInstallSupport;
import org.apache.karaf.features.internal.service.BundleInstallSupportImpl;
import org.apache.karaf.features.internal.service.StateStorage;
+import org.apache.karaf.features.internal.util.SystemExitManager;
import org.apache.karaf.util.ThreadUtils;
import org.apache.karaf.util.tracker.BaseActivator;
import org.apache.karaf.util.tracker.annotation.ProvideService;
@@ -209,7 +210,7 @@ public class Activator extends BaseActivator {
String featuresBoot = getString("featuresBoot", "");
boolean featuresBootAsynchronous = getBoolean("featuresBootAsynchronous", false);
BootFeaturesInstaller bootFeaturesInstaller = new BootFeaturesInstaller(
- bundleContext, featuresService,
+ bundleContext, featuresService, new SystemExitManager(),
featuresRepositories, featuresBoot, featuresBootAsynchronous);
bootFeaturesInstaller.start();
}
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/BootFeaturesInstaller.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/BootFeaturesInstaller.java
index b288e91411..9036360d4d 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/BootFeaturesInstaller.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/BootFeaturesInstaller.java
@@ -18,6 +18,7 @@ package org.apache.karaf.features.internal.service;
import org.apache.karaf.features.BootFinished;
import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.features.internal.util.ExitManager;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,9 +30,11 @@ import java.util.*;
public class BootFeaturesInstaller {
private static final Logger LOGGER = LoggerFactory.getLogger(BootFeaturesInstaller.class);
+ private static final String REQUIRE_SUCCESSFUL_BOOT = "karaf.require.successful.features.boot";
private final FeaturesServiceImpl featuresService;
private final BundleContext bundleContext;
+ private final ExitManager exitManager;
private final String[] repositories;
private final String features;
private final boolean asynchronous;
@@ -53,11 +56,13 @@ public class BootFeaturesInstaller {
public BootFeaturesInstaller(BundleContext bundleContext,
FeaturesServiceImpl featuresService,
+ ExitManager exitManager,
String[] repositories,
String features,
boolean asynchronous) {
this.bundleContext = bundleContext;
this.featuresService = featuresService;
+ this.exitManager = exitManager;
this.repositories = repositories;
this.features = features;
this.asynchronous = asynchronous;
@@ -71,24 +76,33 @@ public class BootFeaturesInstaller {
publishBootFinished();
return;
}
+
+ boolean quitIfUnsuccessful = Boolean.getBoolean(REQUIRE_SUCCESSFUL_BOOT);
+
if (asynchronous) {
new Thread("Initial Features Provisioning") {
public void run() {
- installBootFeatures();
+ installBootFeatures(quitIfUnsuccessful);
}
}.start();
} else {
- installBootFeatures();
+ installBootFeatures(quitIfUnsuccessful);
}
}
- protected void installBootFeatures() {
+ protected void installBootFeatures(boolean quitIfUnsuccessful) {
try {
- addRepositories();
+ addRepositories(quitIfUnsuccessful);
List<Set<String>> stagedFeatures = parseBootFeatures(features);
for (Set<String> features : stagedFeatures) {
- featuresService.installFeatures(features, EnumSet.of(FeaturesService.Option.NoFailOnFeatureNotFound));
+ EnumSet<FeaturesService.Option> options;
+ if (quitIfUnsuccessful) {
+ options = EnumSet.noneOf(FeaturesService.Option.class);
+ } else {
+ options = EnumSet.of(FeaturesService.Option.NoFailOnFeatureNotFound);
+ }
+ featuresService.installFeatures(features, options);
}
featuresService.bootDone();
publishBootFinished();
@@ -104,11 +118,15 @@ public class BootFeaturesInstaller {
return;
}
}
+
LOGGER.error("Error installing boot features", e);
+ if (quitIfUnsuccessful) {
+ exitAfterFailedBoot();
+ }
}
}
- private void addRepositories() {
+ private void addRepositories(boolean quitIfUnsuccessful) {
for (String repo : repositories) {
repo = repo.trim();
if (!repo.isEmpty()) {
@@ -118,6 +136,9 @@ public class BootFeaturesInstaller {
featuresService.addRepository(URI.create(repo));
} catch (Exception e) {
LOGGER.error("Error installing boot feature repository " + repo, e);
+ if (quitIfUnsuccessful) {
+ exitAfterFailedBoot();
+ }
}
}
}
@@ -168,10 +189,11 @@ public class BootFeaturesInstaller {
}
}
+
//-----------------------------------------------------------------------
/**
* Converts all separators to the Unix separator of forward slash.
- *
+ *
* @param path the path to be changed, null ignored
* @return the updated path
*/
@@ -181,13 +203,12 @@ public class BootFeaturesInstaller {
if (path == null || path.indexOf(WINDOWS_SEPARATOR) == -1) {
return path;
}
-
+
path = path.replace(WINDOWS_SEPARATOR, UNIX_SEPARATOR);
LOGGER.debug("Converted path to unix separators: {}", path);
}
return path;
}
-
/**
* Converts all invalid characters in a path to a format supported by {@link URI#create(String)}.
*
@@ -201,4 +222,10 @@ public class BootFeaturesInstaller {
return path.replace(" ", "%20");
}
+
+ private void exitAfterFailedBoot() {
+ LOGGER.error("Exiting Karaf after a failed features boot" +
+ " (as configured by {} in system.properties)", REQUIRE_SUCCESSFUL_BOOT);
+ exitManager.exit();
+ }
}
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/ExitManager.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/ExitManager.java
new file mode 100644
index 0000000000..330548103b
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/util/ExitManager.java
@@ -0,0 +1,24 @@
+package org.apache.karaf.features.internal.util;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+public interface ExitManager {
+
+ void exit();
+}
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/SystemExitManager.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/SystemExitManager.java
new file mode 100644
index 0000000000..bb02d254f7
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/util/SystemExitManager.java
@@ -0,0 +1,27 @@
+package org.apache.karaf.features.internal.util;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+public class SystemExitManager implements ExitManager {
+
+ @Override
+ public void exit() {
+ System.exit(1);
+ }
+}
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/BootFeaturesInstallerTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/BootFeaturesInstallerTest.java
index 3ca9b01019..4e249ce83f 100644
--- a/features/core/src/test/java/org/apache/karaf/features/internal/service/BootFeaturesInstallerTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/BootFeaturesInstallerTest.java
@@ -26,13 +26,16 @@ import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
+import org.apache.karaf.features.FeaturesService;
import org.apache.karaf.features.FeaturesService.Option;
import org.apache.karaf.features.TestBase;
+import org.apache.karaf.features.internal.util.ExitManager;
import org.easymock.Capture;
import org.junit.Assert;
import org.junit.Test;
import static java.util.Arrays.asList;
+import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createStrictMock;
@@ -41,6 +44,7 @@ import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.newCapture;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertTrue;
public class BootFeaturesInstallerTest extends TestBase {
@@ -48,7 +52,7 @@ public class BootFeaturesInstallerTest extends TestBase {
@Test
public void testParser() {
- BootFeaturesInstaller installer = new BootFeaturesInstaller(null, null, new String[0], "", false);
+ BootFeaturesInstaller installer = new BootFeaturesInstaller(null, null, null, new String[0], "", false);
Assert.assertEquals(asList(setOf("test1", "test2"), setOf("test3")), installer.parseBootFeatures(" ( test1 , test2 ) , test3 "));
Assert.assertEquals(Collections.singletonList(setOf("test1", "test2", "test3")), installer.parseBootFeatures(" test1 , test2, test3"));
Assert.assertEquals(asList(setOf("test1"), setOf("test2"), setOf("test3")), installer.parseBootFeatures("(test1), (test2), test3"));
@@ -66,8 +70,8 @@ public class BootFeaturesInstallerTest extends TestBase {
expectLastCall();
replay(impl);
- BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl, new String[0], "config,standard,region", false);
- bootFeatures.installBootFeatures();
+ BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl, null, new String[0], "config,standard,region", false);
+ bootFeatures.installBootFeatures(false);
verify(impl);
List<String> features = new ArrayList<>(featuresCapture.getValue());
@@ -76,6 +80,47 @@ public class BootFeaturesInstallerTest extends TestBase {
Assert.assertEquals("region", features.get(2));
}
+ @Test
+ public void testParseBootFeaturesQuitsWhenFailed() throws Exception {
+ FeaturesServiceImpl impl = createStrictMock(FeaturesServiceImpl.class);
+ MockedExitManager mockedExitManager = new MockedExitManager();
+ BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl, mockedExitManager, new String[0], "config,standard,region,fail(())-me", false);
+ bootFeatures.installBootFeatures(true);
+ assertTrue(mockedExitManager.exitCalled);
+ }
+
+ @Test
+ public void testInstallBootFeatuesQuitsWhenAddingRepositoriesFails() throws Exception {
+ FeaturesServiceImpl impl = createMock(FeaturesServiceImpl.class);
+ impl.addRepository(anyObject());
+ expectLastCall().andThrow(new Exception());
+
+ replay(impl);
+
+ MockedExitManager mockedExitManager = new MockedExitManager();
+ BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl, mockedExitManager, new String[] {"fail-me" },"config,standard,region", false);
+ bootFeatures.installBootFeatures(true);
+
+ assertTrue(mockedExitManager.exitCalled);
+ }
+
+ @Test
+ public void testInstallBootFeatuesQuitsWhenInstallingFeaturesFails() throws Exception {
+ FeaturesServiceImpl impl = createMock(FeaturesServiceImpl.class);
+ impl.installFeatures(anyObject(), eq(EnumSet.noneOf(FeaturesService.Option.class)));
+ expectLastCall().andThrow(new Exception());
+
+ replay(impl);
+
+ MockedExitManager mockedExitManager = new MockedExitManager();
+
+ BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl, mockedExitManager, new String[0], "config,standard,region", false);
+ bootFeatures.installBootFeatures(true);
+ verify(impl);
+
+ assertTrue(mockedExitManager.exitCalled);
+ }
+
@Test
public void testStagedBoot() throws Exception {
FeaturesServiceImpl impl = createStrictMock(FeaturesServiceImpl.class);
@@ -89,8 +134,8 @@ public class BootFeaturesInstallerTest extends TestBase {
expectLastCall();
replay(impl);
- BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl , new String[0], "(transaction), ssh", false);
- bootFeatures.installBootFeatures();
+ BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl, null, new String[0], "(transaction), ssh", false);
+ bootFeatures.installBootFeatures(false);
verify(impl);
}
@@ -105,10 +150,10 @@ public class BootFeaturesInstallerTest extends TestBase {
replay(impl);
String[] repositories = new String[] { INEXISTANT_REPO };
- BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl, repositories, "", false);
+ BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl, null, repositories, "", false);
Logger logger = Logger.getLogger(BootFeaturesInstaller.class.getName());
logger.setLevel(Level.OFF); // Switch off to suppress logging of IllegalArgumentException
- bootFeatures.installBootFeatures();
+ bootFeatures.installBootFeatures(false);
logger.setLevel(Level.INFO);
verify(impl);
}
@@ -116,23 +161,33 @@ public class BootFeaturesInstallerTest extends TestBase {
@Test
public void testParseBootFeatures() throws Exception {
String features = "foo, jim, (ssh, shell, jaas, feature, framework), (system, bundle, management, service), (instance, package, log, deployer, diagnostic, config, kar), bar, zad";
- BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, null, null, null, false);
+ BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, null, null, null, null, false);
List<Set<String>> stages = bootFeatures.parseBootFeatures(features);
Assert.assertEquals(5, stages.size());
for (String f : Arrays.asList("foo", "jim")) {
- Assert.assertTrue("Should contain '" + f + "'", stages.get(0).contains(f));
+ assertTrue("Should contain '" + f + "'", stages.get(0).contains(f));
}
for (String f : Arrays.asList("ssh", "shell", "jaas", "feature", "framework")) {
- Assert.assertTrue("Should contain '" + f + "'", stages.get(1).contains(f));
+ assertTrue("Should contain '" + f + "'", stages.get(1).contains(f));
}
for (String f : Arrays.asList("system", "bundle", "management", "service")) {
- Assert.assertTrue("Should contain '" + f + "'", stages.get(2).contains(f));
+ assertTrue("Should contain '" + f + "'", stages.get(2).contains(f));
}
for (String f : Arrays.asList("instance", "package", "log", "deployer", "diagnostic", "config", "kar")) {
- Assert.assertTrue("Should contain '" + f + "'", stages.get(3).contains(f));
+ assertTrue("Should contain '" + f + "'", stages.get(3).contains(f));
}
for (String f : Arrays.asList("bar", "zad")) {
- Assert.assertTrue("Should contain '" + f + "'", stages.get(4).contains(f));
+ assertTrue("Should contain '" + f + "'", stages.get(4).contains(f));
+ }
+ }
+
+ private static class MockedExitManager implements ExitManager {
+
+ public boolean exitCalled;
+
+ @Override
+ public void exit() {
+ exitCalled = true;
}
}
}
diff --git a/instance/src/main/resources/org/apache/karaf/instance/resources/etc/system.properties b/instance/src/main/resources/org/apache/karaf/instance/resources/etc/system.properties
index c15ee3960a..dcea90287c 100644
--- a/instance/src/main/resources/org/apache/karaf/instance/resources/etc/system.properties
+++ b/instance/src/main/resources/org/apache/karaf/instance/resources/etc/system.properties
@@ -115,6 +115,12 @@ org.apache.aries.proxy.weaving.disabled = org.objectweb.asm.*,org.slf4j.*,org.ap
#
karaf.secured.services = (&(osgi.command.scope=*)(osgi.command.function=*))
+#
+# If set to true, Karaf will exit if either the resolving of feature repositories, or
+# the installation of boot features (as configured in org.apache.karaf.features.cfg) fails.
+#
+karaf.require.successful.features.boot = false
+
#
# By default, if there's no ACL policy for a certain karaf command, this command is allowed to access
# without the RBAC. We can change this behavior by enable the following property, which means