Fix FindBugs warnings in blueprint and enable enforcement
[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 com.google.common.base.Optional;
11 import com.google.common.collect.Lists;
12 import com.google.common.util.concurrent.ThreadFactoryBuilder;
13 import java.lang.management.ManagementFactory;
14 import java.util.AbstractMap.SimpleEntry;
15 import java.util.ArrayList;
16 import java.util.Dictionary;
17 import java.util.Hashtable;
18 import java.util.LinkedHashSet;
19 import java.util.List;
20 import java.util.Map.Entry;
21 import java.util.Set;
22 import java.util.concurrent.CountDownLatch;
23 import java.util.concurrent.ExecutorService;
24 import java.util.concurrent.Executors;
25 import java.util.concurrent.TimeUnit;
26 import javax.annotation.Nullable;
27 import javax.management.InstanceNotFoundException;
28 import javax.management.ObjectName;
29 import javax.xml.parsers.ParserConfigurationException;
30 import org.apache.aries.blueprint.services.BlueprintExtenderService;
31 import org.apache.aries.util.AriesFrameworkUtil;
32 import org.opendaylight.controller.config.api.ConfigRegistry;
33 import org.opendaylight.controller.config.api.ConflictingVersionException;
34 import org.opendaylight.controller.config.api.ModuleIdentifier;
35 import org.opendaylight.controller.config.api.ValidationException;
36 import org.opendaylight.controller.config.facade.xml.ConfigExecution;
37 import org.opendaylight.controller.config.facade.xml.ConfigSubsystemFacade;
38 import org.opendaylight.controller.config.facade.xml.ConfigSubsystemFacadeFactory;
39 import org.opendaylight.controller.config.facade.xml.TestOption;
40 import org.opendaylight.controller.config.facade.xml.mapping.config.Config;
41 import org.opendaylight.controller.config.facade.xml.strategy.EditStrategyType;
42 import org.opendaylight.controller.config.util.ConfigRegistryJMXClient;
43 import org.opendaylight.controller.config.util.xml.DocumentedException;
44 import org.opendaylight.controller.config.util.xml.XmlElement;
45 import org.opendaylight.controller.config.util.xml.XmlMappingConstants;
46 import org.opendaylight.controller.config.util.xml.XmlUtil;
47 import org.osgi.framework.Bundle;
48 import org.osgi.framework.BundleContext;
49 import org.osgi.framework.ServiceReference;
50 import org.osgi.framework.ServiceRegistration;
51 import org.osgi.service.blueprint.container.EventConstants;
52 import org.osgi.service.event.EventHandler;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55 import org.w3c.dom.Document;
56 import org.w3c.dom.Element;
57
58 /**
59  * Implementation of the BlueprintContainerRestartService.
60  *
61  * @author Thomas Pantelis
62  */
63 class BlueprintContainerRestartServiceImpl implements AutoCloseable, BlueprintContainerRestartService {
64     private static final Logger LOG = LoggerFactory.getLogger(BlueprintContainerRestartServiceImpl.class);
65     private static final int CONTAINER_CREATE_TIMEOUT_IN_MINUTES = 5;
66     private static final String CONFIG_MODULE_NAMESPACE_PROP = "config-module-namespace";
67     private static final String CONFIG_MODULE_NAME_PROP = "config-module-name";
68     private static final String CONFIG_INSTANCE_NAME_PROP = "config-instance-name";
69
70     private final ExecutorService restartExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder()
71             .setDaemon(true).setNameFormat("BlueprintContainerRestartService").build());
72     private final BlueprintExtenderService blueprintExtenderService;
73
74     BlueprintContainerRestartServiceImpl(BlueprintExtenderService blueprintExtenderService) {
75         this.blueprintExtenderService = blueprintExtenderService;
76     }
77
78     public void restartContainer(final Bundle bundle, final List<Object> paths) {
79         if (restartExecutor.isShutdown()) {
80             return;
81         }
82
83         LOG.debug("restartContainer for bundle {}", bundle);
84
85         restartExecutor.execute(() -> {
86             blueprintExtenderService.destroyContainer(bundle, blueprintExtenderService.getContainer(bundle));
87             blueprintExtenderService.createContainer(bundle, paths);
88         });
89     }
90
91     @Override
92     public void restartContainerAndDependents(final Bundle bundle) {
93         if (restartExecutor.isShutdown()) {
94             return;
95         }
96
97         LOG.debug("restartContainerAndDependents for bundle {}", bundle);
98
99         restartExecutor.execute(() -> restartContainerAndDependentsInternal(bundle));
100     }
101
102     private void restartContainerAndDependentsInternal(Bundle forBundle) {
103         // We use a LinkedHashSet to preserve insertion order as we walk the service usage hierarchy.
104         Set<Bundle> containerBundlesSet = new LinkedHashSet<>();
105         List<Entry<String, ModuleIdentifier>> configModules = new ArrayList<>();
106         findDependentContainersRecursively(forBundle, containerBundlesSet, configModules);
107
108         List<Bundle> containerBundles = new ArrayList<>(containerBundlesSet);
109
110         LOG.info("Restarting blueprint containers for bundle {} and its dependent bundles {}", forBundle,
111                 containerBundles.subList(1, containerBundles.size()));
112
113         // Destroy the containers in reverse order with 'forBundle' last, ie bottom-up in the service tree.
114         for (Bundle bundle: Lists.reverse(containerBundles)) {
115             blueprintExtenderService.destroyContainer(bundle, blueprintExtenderService.getContainer(bundle));
116         }
117
118         // The blueprint containers are created asynchronously so we register a handler for blueprint events
119         // that are sent when a container is complete, successful or not. The CountDownLatch tells when all
120         // containers are complete. This is done to ensure all blueprint containers are finished before we
121         // restart config modules.
122         final CountDownLatch containerCreationComplete = new CountDownLatch(containerBundles.size());
123         ServiceRegistration<?> eventHandlerReg = registerEventHandler(forBundle.getBundleContext(), event -> {
124             LOG.debug("handleEvent {} for bundle {}", event.getTopic(), event.getProperty(EventConstants.BUNDLE));
125             if (containerBundles.contains(event.getProperty(EventConstants.BUNDLE))) {
126                 containerCreationComplete.countDown();
127             }
128         });
129
130         // Restart the containers top-down starting with 'forBundle'.
131         for (Bundle bundle: containerBundles) {
132             List<Object> paths = BlueprintBundleTracker.findBlueprintPaths(bundle);
133
134             LOG.info("Restarting blueprint container for bundle {} with paths {}", bundle, paths);
135
136             blueprintExtenderService.createContainer(bundle, paths);
137         }
138
139         try {
140             if (!containerCreationComplete.await(CONTAINER_CREATE_TIMEOUT_IN_MINUTES, TimeUnit.MINUTES)) {
141                 LOG.warn("Failed to restart all blueprint containers within {} minutes. Attempted to restart {} {} "
142                         + "but only {} completed restart", CONTAINER_CREATE_TIMEOUT_IN_MINUTES, containerBundles.size(),
143                         containerBundles, containerBundles.size() - containerCreationComplete.getCount());
144                 return;
145             }
146         } catch (InterruptedException e) {
147             LOG.debug("CountDownLatch await was interrupted - returning");
148             return;
149         }
150
151         AriesFrameworkUtil.safeUnregisterService(eventHandlerReg);
152
153         // Now restart any associated config system Modules.
154         restartConfigModules(forBundle.getBundleContext(), configModules);
155     }
156
157     private void restartConfigModules(BundleContext bundleContext, List<Entry<String,
158             ModuleIdentifier>> configModules) {
159         if (configModules.isEmpty()) {
160             return;
161         }
162
163         ServiceReference<ConfigSubsystemFacadeFactory> configFacadeFactoryRef = bundleContext
164                 .getServiceReference(ConfigSubsystemFacadeFactory.class);
165         if (configFacadeFactoryRef == null) {
166             LOG.debug("ConfigSubsystemFacadeFactory service reference not found");
167             return;
168         }
169
170         ConfigSubsystemFacadeFactory configFacadeFactory = bundleContext.getService(configFacadeFactoryRef);
171         if (configFacadeFactory == null) {
172             LOG.debug("ConfigSubsystemFacadeFactory service not found");
173             return;
174         }
175
176         ConfigSubsystemFacade configFacade = configFacadeFactory.createFacade("BlueprintContainerRestartService");
177         try {
178             restartConfigModules(configModules, configFacade);
179         } catch (ParserConfigurationException | DocumentedException | ValidationException
180                 | ConflictingVersionException e) {
181             LOG.error("Error restarting config modules", e);
182         } finally {
183             configFacade.close();
184             bundleContext.ungetService(configFacadeFactoryRef);
185         }
186
187     }
188
189     private void restartConfigModules(List<Entry<String, ModuleIdentifier>> configModules,
190             ConfigSubsystemFacade configFacade) throws ParserConfigurationException, DocumentedException,
191                     ValidationException, ConflictingVersionException {
192
193         Document document = XmlUtil.newDocument();
194         Element dataElement = XmlUtil.createElement(document, XmlMappingConstants.DATA_KEY, Optional.<String>absent());
195         Element modulesElement = XmlUtil.createElement(document, XmlMappingConstants.MODULES_KEY,
196                 Optional.of(XmlMappingConstants.URN_OPENDAYLIGHT_PARAMS_XML_NS_YANG_CONTROLLER_CONFIG));
197         dataElement.appendChild(modulesElement);
198
199         Config configMapping = configFacade.getConfigMapping();
200
201         ConfigRegistry configRegistryClient = new ConfigRegistryJMXClient(ManagementFactory.getPlatformMBeanServer());
202         for (Entry<String, ModuleIdentifier> entry: configModules) {
203             String moduleNamespace = entry.getKey();
204             ModuleIdentifier moduleId = entry.getValue();
205             try {
206                 ObjectName instanceON = configRegistryClient.lookupConfigBean(moduleId.getFactoryName(),
207                         moduleId.getInstanceName());
208
209                 LOG.debug("Found config module instance ObjectName: {}", instanceON);
210
211                 Element moduleElement = configMapping.moduleToXml(moduleNamespace, moduleId.getFactoryName(),
212                         moduleId.getInstanceName(), instanceON, document);
213                 modulesElement.appendChild(moduleElement);
214             } catch (InstanceNotFoundException e) {
215                 LOG.warn("Error looking up config module: namespace {}, module name {}, instance {}",
216                         moduleNamespace, moduleId.getFactoryName(), moduleId.getInstanceName(), e);
217             }
218         }
219
220         if (LOG.isDebugEnabled()) {
221             LOG.debug("Pushing config xml: {}", XmlUtil.toString(dataElement));
222         }
223
224         ConfigExecution execution = new ConfigExecution(configMapping, XmlElement.fromDomElement(dataElement),
225                 TestOption.testThenSet, EditStrategyType.recreate);
226         configFacade.executeConfigExecution(execution);
227         configFacade.commitSilentTransaction();
228     }
229
230     /**
231      * Recursively finds the services registered by the given bundle and the bundles using those services.
232      * User bundles that have an associated blueprint container are added to containerBundles. In addition,
233      * if a registered service has an associated config system Module, as determined via the presence of
234      * certain service properties, the ModuleIdentifier is added to the configModules list.
235      *
236      * @param bundle the bundle to traverse
237      * @param containerBundles the current set of bundles containing blueprint containers
238      */
239     private void findDependentContainersRecursively(Bundle bundle, Set<Bundle> containerBundles,
240             List<Entry<String, ModuleIdentifier>> configModules) {
241         if (!containerBundles.add(bundle)) {
242             // Already seen this bundle...
243             return;
244         }
245
246         ServiceReference<?>[] references = bundle.getRegisteredServices();
247         if (references != null) {
248             for (ServiceReference<?> reference : references) {
249                 possiblyAddConfigModuleIdentifier(reference, configModules);
250
251                 Bundle[] usingBundles = reference.getUsingBundles();
252                 if (usingBundles != null) {
253                     for (Bundle usingBundle : usingBundles) {
254                         if (blueprintExtenderService.getContainer(usingBundle) != null) {
255                             findDependentContainersRecursively(usingBundle, containerBundles, configModules);
256                         }
257                     }
258                 }
259             }
260         }
261     }
262
263     private void possiblyAddConfigModuleIdentifier(ServiceReference<?> reference,
264             List<Entry<String, ModuleIdentifier>> configModules) {
265         Object moduleNamespace = reference.getProperty(CONFIG_MODULE_NAMESPACE_PROP);
266         if (moduleNamespace == null) {
267             return;
268         }
269
270         String moduleName = getRequiredConfigModuleProperty(CONFIG_MODULE_NAME_PROP, moduleNamespace,
271                 reference);
272         String instanceName = getRequiredConfigModuleProperty(CONFIG_INSTANCE_NAME_PROP, moduleNamespace,
273                 reference);
274         if (moduleName == null || instanceName == null) {
275             return;
276         }
277
278         LOG.debug("Found service with config module: namespace {}, module name {}, instance {}",
279                 moduleNamespace, moduleName, instanceName);
280
281         configModules.add(new SimpleEntry<>(moduleNamespace.toString(),
282                 new ModuleIdentifier(moduleName, instanceName)));
283     }
284
285     @Nullable
286     private String getRequiredConfigModuleProperty(String propName, Object moduleNamespace,
287             ServiceReference<?> reference) {
288         Object value = reference.getProperty(propName);
289         if (value == null) {
290             LOG.warn(
291                 "OSGi service with {} property is missing property {} therefore the config module can't be restarted",
292                 CONFIG_MODULE_NAMESPACE_PROP, propName);
293             return null;
294         }
295
296         return value.toString();
297     }
298
299     private ServiceRegistration<?> registerEventHandler(BundleContext bundleContext, EventHandler handler) {
300         Dictionary<String, Object> props = new Hashtable<>();
301         props.put(org.osgi.service.event.EventConstants.EVENT_TOPIC,
302                 new String[]{EventConstants.TOPIC_CREATED, EventConstants.TOPIC_FAILURE});
303         return bundleContext.registerService(EventHandler.class.getName(), handler, props);
304     }
305
306     @Override
307     public void close() {
308         restartExecutor.shutdownNow();
309     }
310 }