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


Reply via email to