Author: pauls Date: Wed Feb 15 23:17:25 2017 New Revision: 1783162 URL: http://svn.apache.org/viewvc?rev=1783162&view=rev Log: Make the BundleUpdateTask retry failed bundle updates (SLING-5457).
Added: sling/trunk/installer/it/src/test/java/org/apache/sling/installer/it/BundleInstallUpgradeExceptionRetryTest.java Modified: sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/tasks/BundleUpdateTask.java sling/trunk/installer/it/pom.xml Modified: sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/tasks/BundleUpdateTask.java URL: http://svn.apache.org/viewvc/sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/tasks/BundleUpdateTask.java?rev=1783162&r1=1783161&r2=1783162&view=diff ============================================================================== --- sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/tasks/BundleUpdateTask.java (original) +++ sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/tasks/BundleUpdateTask.java Wed Feb 15 23:17:25 2017 @@ -36,6 +36,11 @@ public class BundleUpdateTask extends Ab private static final String BUNDLE_UPDATE_ORDER = "50-"; + private static final int MAX_RETRIES = 5; + + // keep track of retry attempts via temporary attribute stored in taskresource + private String ATTR_UPDATE_RETRY = "org.apache.sling.installer.core.impl.tasks.BundleUpdateTask.retrycount"; + public BundleUpdateTask(final TaskResourceGroup r, final TaskSupport creator) { super(r, creator); @@ -73,17 +78,17 @@ public class BundleUpdateTask extends Ab // Do not update if same version, unless snapshot boolean snapshot = false; - final Version currentVersion = b.getVersion(); - snapshot = BundleInfo.isSnapshot(newVersion); - if (currentVersion.equals(newVersion) && !snapshot) { - // TODO : Isn't this already checked in the task creator? - String message = MessageFormat.format("Same version is already installed, and not a snapshot, ignoring update: {0}", getResource()); - this.getLogger().debug(message); - this.setFinishedState(ResourceState.INSTALLED, null, message); - return; - } + final Version currentVersion = b.getVersion(); + snapshot = BundleInfo.isSnapshot(newVersion); + if (currentVersion.equals(newVersion) && !snapshot) { + // TODO : Isn't this already checked in the task creator? + String message = MessageFormat.format("Same version is already installed, and not a snapshot, ignoring update: {0}", getResource()); + this.getLogger().debug(message); + this.setFinishedState(ResourceState.INSTALLED, null, message); + return; + } - try { + try { // If the bundle is active before the update - restart it once updated, but // in sequence, not right now final boolean reactivate = this.isBundleActive(b); @@ -131,11 +136,24 @@ public class BundleUpdateTask extends Ab } else { this.setFinishedState(ResourceState.INSTALLED); } - } catch (final Exception e) { - String message = MessageFormat.format("Removing failing update task due to {0} - unable to retry: {1}", e.getLocalizedMessage(), this); - this.getLogger().warn(message, e); - this.setFinishedState(ResourceState.IGNORED, null, message); - } + } catch (final Exception e) { + int retries = 0; + Object obj = getResource().getTemporaryAttribute(ATTR_UPDATE_RETRY); + if (obj instanceof Integer) { + retries = (Integer) obj; + } + getResource().setTemporaryAttribute(ATTR_UPDATE_RETRY, Integer.valueOf(++retries)); + if (retries > MAX_RETRIES) { + String message = MessageFormat.format("Removing failing update task due to {0} - unable to retry: {1}", + e.getLocalizedMessage(), this); + this.getLogger().error(message, e); + this.setFinishedState(ResourceState.IGNORED, null, message); + } else { + String message = MessageFormat.format("Failing update task due to {0} - will retry up to {1} more time(s) for {2} later", + e.getLocalizedMessage(), MAX_RETRIES - (retries - 1) , this); + this.getLogger().warn(message, e); + } + } } @Override Modified: sling/trunk/installer/it/pom.xml URL: http://svn.apache.org/viewvc/sling/trunk/installer/it/pom.xml?rev=1783162&r1=1783161&r2=1783162&view=diff ============================================================================== --- sling/trunk/installer/it/pom.xml (original) +++ sling/trunk/installer/it/pom.xml Wed Feb 15 23:17:25 2017 @@ -26,7 +26,7 @@ </parent> <artifactId>org.apache.sling.installer.it</artifactId> - <version>3.8.1-SNAPSHOT</version> + <version>3.8.3-SNAPSHOT</version> <packaging>jar</packaging> <name>Apache Sling Installer Integration Tests</name> Added: sling/trunk/installer/it/src/test/java/org/apache/sling/installer/it/BundleInstallUpgradeExceptionRetryTest.java URL: http://svn.apache.org/viewvc/sling/trunk/installer/it/src/test/java/org/apache/sling/installer/it/BundleInstallUpgradeExceptionRetryTest.java?rev=1783162&view=auto ============================================================================== --- sling/trunk/installer/it/src/test/java/org/apache/sling/installer/it/BundleInstallUpgradeExceptionRetryTest.java (added) +++ sling/trunk/installer/it/src/test/java/org/apache/sling/installer/it/BundleInstallUpgradeExceptionRetryTest.java Wed Feb 15 23:17:25 2017 @@ -0,0 +1,215 @@ +/* + * 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. + */ +package org.apache.sling.installer.it; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ops4j.pax.exam.Option; +import org.ops4j.pax.exam.junit.PaxExam; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.framework.SynchronousBundleListener; +import org.osgi.framework.Version; + +@RunWith(PaxExam.class) +public class BundleInstallUpgradeExceptionRetryTest extends OsgiInstallerTestBase { + + @org.ops4j.pax.exam.Configuration + public Option[] config() { + return defaultConfiguration(); + } + + @Before + public void beforeTest() throws Exception { + setupInstaller(); + + final Object listener = this.startObservingBundleEvents(); + installer.updateResources(URL_SCHEME, getInstallableResource(createTestBundle(new Version("1.0.0"))), null); + + this.waitForBundleEvents(symbolicName + " should be installed with version 1.0.0", listener, + new BundleEvent(symbolicName, "1.0.0", org.osgi.framework.BundleEvent.STARTED)); + + assertBundle("After installing", symbolicName, "1.0.0", Bundle.ACTIVE); + } + + static final String symbolicName = "osgi-installer-testbundle"; + + @After + public void afterTest() throws BundleException { + super.tearDown(); + } + + @Test + public void testUpdateFailsWithMoreThanMaxRetrys() throws Exception { + // install version 1.1 and fail activation with exception all attempts + final Object listener = this.startObservingBundleEvents(); + final AtomicReference<ServiceRegistration<AtomicInteger>> ref = new AtomicReference<ServiceRegistration<AtomicInteger>>( + null); + final AtomicInteger counter = new AtomicInteger(5); + bundleContext.addBundleListener(new SynchronousBundleListener() { + + @Override + public void bundleChanged(org.osgi.framework.BundleEvent event) { + if (event.getType() == org.osgi.framework.BundleEvent.STOPPED + && event.getBundle().getSymbolicName().equals(symbolicName) + && event.getBundle().getVersion().equals(new Version("1.0.0"))) { + System.out.println(event.getSource() + " " + event.getType()); + Thread.dumpStack(); + if (ref.get() == null) { + try { + event.getBundle().start(); + ref.set(bundleContext.registerService(AtomicInteger.class, counter, null)); + } catch (BundleException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } else { + ref.getAndSet(null).unregister(); + if (counter.get() == 0) { + bundleContext.removeBundleListener(this); + } + } + } + } + }); + + installer.updateResources(URL_SCHEME, getInstallableResource(createTestBundle(new Version("2.0.0"))), null); + + try { + long time = 0; + while (counter.get() >= 0 && time < 1000) { + sleep(100); + time += 100; + } + + assertBundle("After installing", symbolicName, "1.0.0", Bundle.ACTIVE); + } finally { + if (ref.get() != null) { + ref.get().unregister(); + } + } + } + + @Test + public void testUpdateSuccedsWithLessThanMaxRetrys() throws Exception { + // install version 1.1 and fail activation with exception all attempts + final Object listener = this.startObservingBundleEvents(); + final AtomicReference<ServiceRegistration<AtomicInteger>> ref = new AtomicReference<ServiceRegistration<AtomicInteger>>( + null); + final AtomicInteger counter = new AtomicInteger(3); + bundleContext.addBundleListener(new SynchronousBundleListener() { + + @Override + public void bundleChanged(org.osgi.framework.BundleEvent event) { + if (event.getType() == org.osgi.framework.BundleEvent.STOPPED + && event.getBundle().getSymbolicName().equals(symbolicName) + && event.getBundle().getVersion().equals(new Version("1.0.0"))) { + if (ref.get() == null) { + try { + event.getBundle().start(); + ref.set(bundleContext.registerService(AtomicInteger.class, counter, null)); + } catch (BundleException e) { + } + } else { + ref.getAndSet(null).unregister(); + if (counter.get() == 0) { + bundleContext.removeBundleListener(this); + } + } + } + } + }); + + installer.updateResources(URL_SCHEME, getInstallableResource(createTestBundle(new Version("2.0.0"))), null); + + try { + long time = 0; + while (counter.get() >= 0 && time < 1000) { + sleep(100); + time += 100; + } + + assertBundle("After installing", symbolicName, "2.0.0", Bundle.ACTIVE); + } finally { + if (ref.get() != null) { + ref.get().unregister(); + } + } + } + + private static File createTestBundle(Version version) throws IOException { + String manifest = "Bundle-SymbolicName: " + symbolicName + "\n" + "Bundle-Version: " + version + "\n" + + "Bundle-ManifestVersion: 2\n" + "Bundle-Activator: " + ThrowingActivator.class.getName() + "\n" + + "Import-Package: org.osgi.framework\n" + "Manifest-Version: 1.0\n\n"; + return createBundle("testbundle", manifest, ThrowingActivator.class); + } + + private static File createBundle(String name, String manifest, Class... classes) throws IOException { + File f = File.createTempFile(name, ".jar"); + f.deleteOnExit(); + + Manifest mf = new Manifest(new ByteArrayInputStream(manifest.getBytes("utf-8"))); + JarOutputStream os = new JarOutputStream(new FileOutputStream(f), mf); + + for (Class clazz : classes) { + String path = clazz.getName().replace('.', '/') + ".class"; + os.putNextEntry(new ZipEntry(path)); + + InputStream is = clazz.getClassLoader().getResourceAsStream(path); + byte[] buffer = new byte[8 * 1024]; + for (int i = is.read(buffer); i != -1; i = is.read(buffer)) { + os.write(buffer, 0, i); + } + is.close(); + os.closeEntry(); + } + os.close(); + return f; + } + + public static class ThrowingActivator implements BundleActivator { + @Override + public void start(BundleContext context) throws Exception { + + } + + @Override + public void stop(BundleContext context) throws Exception { + ServiceReference<AtomicInteger> ref = context.getServiceReference(AtomicInteger.class); + if (ref != null && context.getService(ref).getAndDecrement() >= 0) { + throw new Exception("Force exception for update"); + } + } + } +}