0841358b7ef08933e7bb1537e0ac036e7129f37f
[controller.git] / opendaylight / blueprint / src / main / java / org / opendaylight / controller / blueprint / BlueprintContainerRestartServiceImpl.java
1 /*
2  * Copyright (c) 2016 Brocade Communications Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.blueprint;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.collect.Lists;
13 import com.google.common.util.concurrent.ThreadFactoryBuilder;
14 import java.util.ArrayDeque;
15 import java.util.ArrayList;
16 import java.util.Arrays;
17 import java.util.Collections;
18 import java.util.Deque;
19 import java.util.Hashtable;
20 import java.util.LinkedHashSet;
21 import java.util.List;
22 import java.util.Set;
23 import java.util.concurrent.CountDownLatch;
24 import java.util.concurrent.ExecutorService;
25 import java.util.concurrent.Executors;
26 import java.util.concurrent.TimeUnit;
27 import org.apache.aries.blueprint.services.BlueprintExtenderService;
28 import org.apache.aries.quiesce.participant.QuiesceParticipant;
29 import org.apache.aries.util.AriesFrameworkUtil;
30 import org.gaul.modernizer_maven_annotations.SuppressModernizer;
31 import org.osgi.framework.Bundle;
32 import org.osgi.framework.BundleContext;
33 import org.osgi.framework.ServiceReference;
34 import org.osgi.framework.ServiceRegistration;
35 import org.osgi.service.blueprint.container.BlueprintEvent;
36 import org.osgi.service.blueprint.container.BlueprintListener;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 /**
41  * Implementation of the BlueprintContainerRestartService.
42  *
43  * @author Thomas Pantelis
44  */
45 class BlueprintContainerRestartServiceImpl implements AutoCloseable, BlueprintContainerRestartService {
46     private static final Logger LOG = LoggerFactory.getLogger(BlueprintContainerRestartServiceImpl.class);
47     private static final int CONTAINER_CREATE_TIMEOUT_IN_MINUTES = 5;
48
49     private final ExecutorService restartExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder()
50             .setDaemon(true).setNameFormat("BlueprintContainerRestartService").build());
51
52     private BlueprintExtenderService blueprintExtenderService;
53     private QuiesceParticipant quiesceParticipant;
54
55     void setBlueprintExtenderService(final BlueprintExtenderService blueprintExtenderService) {
56         this.blueprintExtenderService = blueprintExtenderService;
57     }
58
59     void setQuiesceParticipant(final QuiesceParticipant quiesceParticipant) {
60         this.quiesceParticipant = quiesceParticipant;
61     }
62
63     public void restartContainer(final Bundle bundle, final List<Object> paths) {
64         LOG.debug("restartContainer for bundle {}", bundle);
65
66         if (restartExecutor.isShutdown()) {
67             LOG.debug("Already closed - returning");
68             return;
69         }
70
71         restartExecutor.execute(() -> {
72             blueprintExtenderService.destroyContainer(bundle, blueprintExtenderService.getContainer(bundle));
73             blueprintExtenderService.createContainer(bundle, paths);
74         });
75     }
76
77     @Override
78     public void restartContainerAndDependents(final Bundle bundle) {
79         if (restartExecutor.isShutdown()) {
80             return;
81         }
82
83         LOG.debug("restartContainerAndDependents for bundle {}", bundle);
84
85         restartExecutor.execute(() -> restartContainerAndDependentsInternal(bundle));
86     }
87
88     private void restartContainerAndDependentsInternal(final Bundle forBundle) {
89         requireNonNull(blueprintExtenderService);
90         requireNonNull(quiesceParticipant);
91
92         // We use a LinkedHashSet to preserve insertion order as we walk the service usage hierarchy.
93         Set<Bundle> containerBundlesSet = new LinkedHashSet<>();
94         findDependentContainersRecursively(forBundle, containerBundlesSet);
95
96         List<Bundle> containerBundles = new ArrayList<>(containerBundlesSet);
97
98         LOG.info("Restarting blueprint containers for bundle {} and its dependent bundles {}", forBundle,
99                 containerBundles.subList(1, containerBundles.size()));
100
101         // The blueprint containers are created asynchronously so we register a handler for blueprint events
102         // that are sent when a container is complete, successful or not. The CountDownLatch tells when all
103         // containers are complete. This is done to ensure all blueprint containers are finished before we
104         // restart config modules.
105         final CountDownLatch containerCreationComplete = new CountDownLatch(containerBundles.size());
106         ServiceRegistration<?> eventHandlerReg = registerEventHandler(forBundle.getBundleContext(), event -> {
107             final Bundle bundle = event.getBundle();
108             if (event.isReplay()) {
109                 LOG.trace("Got replay BlueprintEvent {} for bundle {}", event.getType(), bundle);
110                 return;
111             }
112
113             LOG.debug("Got BlueprintEvent {} for bundle {}", event.getType(), bundle);
114             if (containerBundles.contains(bundle)
115                     && (event.getType() == BlueprintEvent.CREATED || event.getType() == BlueprintEvent.FAILURE)) {
116                 containerCreationComplete.countDown();
117                 LOG.debug("containerCreationComplete is now {}", containerCreationComplete.getCount());
118             }
119         });
120
121         final Runnable createContainerCallback = () -> createContainers(containerBundles);
122
123         // Destroy the container down-top recursively and once done, restart the container top-down
124         destroyContainers(new ArrayDeque<>(Lists.reverse(containerBundles)), createContainerCallback);
125
126
127         try {
128             if (!containerCreationComplete.await(CONTAINER_CREATE_TIMEOUT_IN_MINUTES, TimeUnit.MINUTES)) {
129                 LOG.warn("Failed to restart all blueprint containers within {} minutes. Attempted to restart {} {} "
130                         + "but only {} completed restart", CONTAINER_CREATE_TIMEOUT_IN_MINUTES, containerBundles.size(),
131                         containerBundles, containerBundles.size() - containerCreationComplete.getCount());
132                 return;
133             }
134         } catch (final InterruptedException e) {
135             LOG.debug("CountDownLatch await was interrupted - returning");
136             return;
137         }
138
139         AriesFrameworkUtil.safeUnregisterService(eventHandlerReg);
140
141         LOG.info("Finished restarting blueprint containers for bundle {} and its dependent bundles", forBundle);
142     }
143
144     /**
145      * Recursively quiesce and destroy the bundles one by one in order to maintain synchronicity and ordering.
146      * @param remainingBundlesToDestroy the list of remaining bundles to destroy.
147      * @param createContainerCallback a {@link Runnable} to {@code run()} when the recursive function is completed.
148      */
149     private void destroyContainers(final Deque<Bundle> remainingBundlesToDestroy,
150             final Runnable createContainerCallback) {
151
152         final Bundle nextBundle;
153         synchronized (remainingBundlesToDestroy) {
154             if (remainingBundlesToDestroy.isEmpty()) {
155                 LOG.debug("All blueprint containers were quiesced and destroyed");
156                 createContainerCallback.run();
157                 return;
158             }
159
160             nextBundle = remainingBundlesToDestroy.poll();
161         }
162
163         // The Quiesce capability is a like a soft-stop, clean-stop. In the case of the Blueprint extender, in flight
164         // service calls are allowed to finish; they're counted in and counted out, and no new calls are allowed. When
165         // there are no in flight service calls, the bundle is told to stop. The Blueprint bundle itself doesn't know
166         // this is happening which is a key design point. In the case of Blueprint, the extender ensures no new Entity
167         // Managers(EMs) are created. Then when all those EMs are closed the quiesce operation reports that it is
168         // finished.
169         // To properly restart the blueprint containers, first we have to quiesce the list of bundles, and once done, it
170         // is safe to destroy their BlueprintContainer, so no reference is retained.
171         //
172         // Mail - thread explaining Quiesce API:
173         //      https://www.mail-archive.com/dev@aries.apache.org/msg08403.html
174
175         // Quiesced the bundle to unregister the associated BlueprintContainer
176         quiesceParticipant.quiesce(bundlesQuiesced -> {
177
178             // Destroy the container once Quiesced
179             Arrays.stream(bundlesQuiesced).forEach(quiescedBundle -> {
180                 LOG.debug("Quiesced bundle {}", quiescedBundle);
181                 blueprintExtenderService.destroyContainer(
182                         quiescedBundle, blueprintExtenderService.getContainer(quiescedBundle));
183             });
184
185             destroyContainers(remainingBundlesToDestroy, createContainerCallback);
186
187         }, Collections.singletonList(nextBundle));
188     }
189
190     private void createContainers(final List<Bundle> containerBundles) {
191         containerBundles.forEach(bundle -> {
192             List<Object> paths = BlueprintBundleTracker.findBlueprintPaths(bundle);
193
194             LOG.info("Restarting blueprint container for bundle {} with paths {}", bundle, paths);
195
196             blueprintExtenderService.createContainer(bundle, paths);
197         });
198     }
199
200     /**
201      * Recursively finds the services registered by the given bundle and the bundles using those services.
202      * User bundles that have an associated blueprint container are added to containerBundles.
203      *
204      * @param bundle the bundle to traverse
205      * @param containerBundles the current set of bundles containing blueprint containers
206      */
207     private void findDependentContainersRecursively(final Bundle bundle, final Set<Bundle> containerBundles) {
208         if (!containerBundles.add(bundle)) {
209             // Already seen this bundle...
210             return;
211         }
212
213         ServiceReference<?>[] references = bundle.getRegisteredServices();
214         if (references != null) {
215             for (ServiceReference<?> reference : references) {
216                 Bundle[] usingBundles = reference.getUsingBundles();
217                 if (usingBundles != null) {
218                     for (Bundle usingBundle : usingBundles) {
219                         if (blueprintExtenderService.getContainer(usingBundle) != null) {
220                             findDependentContainersRecursively(usingBundle, containerBundles);
221                         }
222                     }
223                 }
224             }
225         }
226     }
227
228     @SuppressModernizer
229     private static ServiceRegistration<?> registerEventHandler(final BundleContext bundleContext,
230             final BlueprintListener listener) {
231         return bundleContext.registerService(BlueprintListener.class.getName(), listener, new Hashtable<>());
232     }
233
234     @Override
235     public void close() {
236         LOG.debug("Closing");
237
238         restartExecutor.shutdownNow();
239     }
240 }