ONOS-5101: Ensure app activation order reflects the dependency order

Change-Id: I77e0579436d80643b8262f0ec5ad6efb57936a0b
diff --git a/core/store/dist/src/main/java/org/onosproject/store/app/DistributedApplicationStore.java b/core/store/dist/src/main/java/org/onosproject/store/app/DistributedApplicationStore.java
index a043814..9f9cb2d 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/app/DistributedApplicationStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/app/DistributedApplicationStore.java
@@ -30,7 +30,6 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
-import org.onlab.util.Tools;
 import org.onosproject.app.ApplicationDescription;
 import org.onosproject.app.ApplicationEvent;
 import org.onosproject.app.ApplicationException;
@@ -49,6 +48,9 @@
 import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
 import org.onosproject.store.cluster.messaging.MessageSubject;
 import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.AtomicValue;
+import org.onosproject.store.service.AtomicValueEvent;
+import org.onosproject.store.service.AtomicValueEventListener;
 import org.onosproject.store.service.ConsistentMap;
 import org.onosproject.store.service.MapEvent;
 import org.onosproject.store.service.MapEventListener;
@@ -119,6 +121,7 @@
     private ExecutorService messageHandlingExecutor;
 
     private ConsistentMap<ApplicationId, InternalApplicationHolder> apps;
+    private AtomicValue<Application> nextAppToActivate;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ClusterCommunicationService clusterCommunicator;
@@ -133,6 +136,7 @@
     protected ApplicationIdStore idStore;
 
     private final InternalAppsListener appsListener = new InternalAppsListener();
+    private final NextAppToActivateValueListener nextAppToActivateListener = new NextAppToActivateValueListener();
 
     private Consumer<Status> statusChangeListener;
 
@@ -168,6 +172,14 @@
                                                  InternalState.class))
                 .build();
 
+        nextAppToActivate = storageService.<Application>atomicValueBuilder()
+                .withName("onos-apps-activation-order-value")
+                .withSerializer(Serializer.using(KryoNamespaces.API))
+                .build()
+                .asAtomicValue();
+
+        nextAppToActivate.addListener(nextAppToActivateListener);
+
         executor = newSingleThreadScheduledExecutor(groupedThreads("onos/app", "store", log));
         statusChangeListener = status -> {
             if (status == Status.ACTIVE) {
@@ -248,6 +260,7 @@
         clusterCommunicator.removeSubscriber(APP_BITS_REQUEST);
         apps.removeStatusChangeListener(statusChangeListener);
         apps.removeListener(appsListener);
+        nextAppToActivate.removeListener(nextAppToActivateListener);
         messageHandlingExecutor.shutdown();
         executor.shutdown();
         log.info("Stopped");
@@ -346,15 +359,10 @@
             }
             activateRequiredApps(vAppHolder.value().app());
 
-            // FIXME: Take a breath before the post-order operation to allow required app
-            // activation events to fully propagate. There appears to be an out-of-order
-            // event delivery issue that needs to be fixed.
-            Tools.delay(FIXME_ACTIVATION_DELAY);
-
             apps.computeIf(appId, v -> v != null && v.state() != ACTIVATED,
                     (k, v) -> new InternalApplicationHolder(
                             v.app(), ACTIVATED, v.permissions()));
-
+            nextAppToActivate.set(vAppHolder.value().app());
         }
     }
 
@@ -427,6 +435,20 @@
         }
     }
 
+    private class NextAppToActivateValueListener implements AtomicValueEventListener<Application> {
+
+        @Override
+        public void event(AtomicValueEvent<Application> event) {
+            messageHandlingExecutor.execute(() -> {
+                Application app = event.newValue();
+                String appName = app.id().name();
+                installAppIfNeeded(app);
+                setActive(appName);
+                delegate.notify(new ApplicationEvent(APP_ACTIVATED, app));
+            });
+        }
+    }
+
     /**
      * Listener to application state distributed map changes.
      */
@@ -454,13 +476,10 @@
     }
 
     private void setupApplicationAndNotify(ApplicationId appId, Application app, InternalState state) {
+        // ACTIVATED state is handled separately in NextAppToActivateValueListener
         if (state == INSTALLED) {
             fetchBitsIfNeeded(app);
             delegate.notify(new ApplicationEvent(APP_INSTALLED, app));
-        } else if (state == ACTIVATED) {
-            installAppIfNeeded(app);
-            setActive(appId.name());
-            delegate.notify(new ApplicationEvent(APP_ACTIVATED, app));
         } else if (state == DEACTIVATED) {
             clearActive(appId.name());
             delegate.notify(new ApplicationEvent(APP_DEACTIVATED, app));