2 * Copyright (c) 2016 Brocade Communications Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.controller.blueprint;
10 import static java.util.Objects.requireNonNull;
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;
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;
41 * Implementation of the BlueprintContainerRestartService.
43 * @author Thomas Pantelis
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;
49 private final ExecutorService restartExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder()
50 .setDaemon(true).setNameFormat("BlueprintContainerRestartService").build());
52 private BlueprintExtenderService blueprintExtenderService;
53 private QuiesceParticipant quiesceParticipant;
55 void setBlueprintExtenderService(final BlueprintExtenderService blueprintExtenderService) {
56 this.blueprintExtenderService = blueprintExtenderService;
59 void setQuiesceParticipant(final QuiesceParticipant quiesceParticipant) {
60 this.quiesceParticipant = quiesceParticipant;
63 public void restartContainer(final Bundle bundle, final List<Object> paths) {
64 LOG.debug("restartContainer for bundle {}", bundle);
66 if (restartExecutor.isShutdown()) {
67 LOG.debug("Already closed - returning");
71 restartExecutor.execute(() -> {
72 blueprintExtenderService.destroyContainer(bundle, blueprintExtenderService.getContainer(bundle));
73 blueprintExtenderService.createContainer(bundle, paths);
78 public void restartContainerAndDependents(final Bundle bundle) {
79 if (restartExecutor.isShutdown()) {
83 LOG.debug("restartContainerAndDependents for bundle {}", bundle);
85 restartExecutor.execute(() -> restartContainerAndDependentsInternal(bundle));
88 private void restartContainerAndDependentsInternal(final Bundle forBundle) {
89 requireNonNull(blueprintExtenderService);
90 requireNonNull(quiesceParticipant);
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);
96 List<Bundle> containerBundles = new ArrayList<>(containerBundlesSet);
98 LOG.info("Restarting blueprint containers for bundle {} and its dependent bundles {}", forBundle,
99 containerBundles.subList(1, containerBundles.size()));
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);
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());
121 final Runnable createContainerCallback = () -> createContainers(containerBundles);
123 // Destroy the container down-top recursively and once done, restart the container top-down
124 destroyContainers(new ArrayDeque<>(Lists.reverse(containerBundles)), createContainerCallback);
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());
134 } catch (final InterruptedException e) {
135 LOG.debug("CountDownLatch await was interrupted - returning");
139 AriesFrameworkUtil.safeUnregisterService(eventHandlerReg);
141 LOG.info("Finished restarting blueprint containers for bundle {} and its dependent bundles", forBundle);
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.
149 private void destroyContainers(final Deque<Bundle> remainingBundlesToDestroy,
150 final Runnable createContainerCallback) {
152 final Bundle nextBundle;
153 synchronized (remainingBundlesToDestroy) {
154 if (remainingBundlesToDestroy.isEmpty()) {
155 LOG.debug("All blueprint containers were quiesced and destroyed");
156 createContainerCallback.run();
160 nextBundle = remainingBundlesToDestroy.poll();
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
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.
172 // Mail - thread explaining Quiesce API:
173 // https://www.mail-archive.com/dev@aries.apache.org/msg08403.html
175 // Quiesced the bundle to unregister the associated BlueprintContainer
176 quiesceParticipant.quiesce(bundlesQuiesced -> {
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));
185 destroyContainers(remainingBundlesToDestroy, createContainerCallback);
187 }, Collections.singletonList(nextBundle));
190 private void createContainers(final List<Bundle> containerBundles) {
191 containerBundles.forEach(bundle -> {
192 List<Object> paths = BlueprintBundleTracker.findBlueprintPaths(bundle);
194 LOG.info("Restarting blueprint container for bundle {} with paths {}", bundle, paths);
196 blueprintExtenderService.createContainer(bundle, paths);
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.
204 * @param bundle the bundle to traverse
205 * @param containerBundles the current set of bundles containing blueprint containers
207 private void findDependentContainersRecursively(final Bundle bundle, final Set<Bundle> containerBundles) {
208 if (!containerBundles.add(bundle)) {
209 // Already seen this bundle...
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);
229 private static ServiceRegistration<?> registerEventHandler(final BundleContext bundleContext,
230 final BlueprintListener listener) {
231 return bundleContext.registerService(BlueprintListener.class.getName(), listener, new Hashtable<>());
235 public void close() {
236 LOG.debug("Closing");
238 restartExecutor.shutdownNow();