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");
+ }
+ }
+ }
+}