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