2 * Copyright (c) 2013 Cisco 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.config.manager.impl;
10 import java.util.ArrayList;
11 import java.util.Collection;
12 import java.util.Collections;
13 import java.util.HashMap;
14 import java.util.Iterator;
15 import java.util.LinkedList;
16 import java.util.List;
18 import java.util.Map.Entry;
21 import javax.annotation.concurrent.GuardedBy;
22 import javax.annotation.concurrent.NotThreadSafe;
23 import javax.annotation.concurrent.ThreadSafe;
24 import javax.management.InstanceAlreadyExistsException;
25 import javax.management.InstanceNotFoundException;
26 import javax.management.MBeanServer;
27 import javax.management.MBeanServerFactory;
28 import javax.management.ObjectName;
30 import org.opendaylight.controller.config.api.ConflictingVersionException;
31 import org.opendaylight.controller.config.api.ModuleIdentifier;
32 import org.opendaylight.controller.config.api.RuntimeBeanRegistratorAwareModule;
33 import org.opendaylight.controller.config.api.ValidationException;
34 import org.opendaylight.controller.config.api.jmx.CommitStatus;
35 import org.opendaylight.controller.config.api.jmx.ConfigRegistryMXBean;
36 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
37 import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicReadableWrapper;
38 import org.opendaylight.controller.config.manager.impl.factoriesresolver.HierarchicalConfigMBeanFactoriesHolder;
39 import org.opendaylight.controller.config.manager.impl.factoriesresolver.ModuleFactoriesResolver;
40 import org.opendaylight.controller.config.manager.impl.jmx.BaseJMXRegistrator;
41 import org.opendaylight.controller.config.manager.impl.jmx.ModuleJMXRegistrator;
42 import org.opendaylight.controller.config.manager.impl.jmx.RootRuntimeBeanRegistratorImpl;
43 import org.opendaylight.controller.config.manager.impl.jmx.TransactionJMXRegistrator;
44 import org.opendaylight.controller.config.manager.impl.osgi.BeanToOsgiServiceManager;
45 import org.opendaylight.controller.config.manager.impl.osgi.BeanToOsgiServiceManager.OsgiRegistration;
46 import org.opendaylight.controller.config.manager.impl.util.LookupBeansUtil;
47 import org.opendaylight.controller.config.spi.Module;
48 import org.osgi.framework.BundleContext;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
53 * Singleton that is responsible for creating and committing Config
54 * Transactions. It is registered in Platform MBean Server.
57 public class ConfigRegistryImpl implements AutoCloseable,
58 ConfigRegistryImplMXBean {
59 private static final Logger logger = LoggerFactory
60 .getLogger(ConfigRegistryImpl.class);
62 private final ModuleFactoriesResolver resolver;
63 private final MBeanServer configMBeanServer;
65 private long version = 0;
67 private long versionCounter = 0;
70 * Contains current configuration in form of {moduleName:{instanceName,read
71 * only module}} for copying state to new transaction. Each running module
72 * is present just once, no matter how many interfaces it exposes.
75 private final ConfigHolder currentConfig = new ConfigHolder();
78 * Will return true unless there was a transaction that succeeded during
79 * validation but failed in second phase of commit. In this case the server
80 * is unstable and its state is undefined.
83 private boolean isHealthy = true;
86 * Holds Map<transactionName, transactionController> and purges it each time
87 * its content is requested.
90 private final TransactionsHolder transactionsHolder = new TransactionsHolder();
92 private final BaseJMXRegistrator baseJMXRegistrator;
94 private final BeanToOsgiServiceManager beanToOsgiServiceManager;
96 // internal jmx server for read only beans
97 private final MBeanServer registryMBeanServer;
98 // internal jmx server shared by all transactions
99 private final MBeanServer transactionsMBeanServer;
102 public ConfigRegistryImpl(ModuleFactoriesResolver resolver,
103 BundleContext bundleContext, MBeanServer configMBeanServer) {
104 this(resolver, bundleContext, configMBeanServer,
105 new BaseJMXRegistrator(configMBeanServer));
109 public ConfigRegistryImpl(ModuleFactoriesResolver resolver,
110 BundleContext bundleContext, MBeanServer configMBeanServer,
111 BaseJMXRegistrator baseJMXRegistrator) {
112 this.resolver = resolver;
113 this.beanToOsgiServiceManager = new BeanToOsgiServiceManager(
115 this.configMBeanServer = configMBeanServer;
116 this.baseJMXRegistrator = baseJMXRegistrator;
117 this.registryMBeanServer = MBeanServerFactory
118 .createMBeanServer("ConfigRegistry" + configMBeanServer.getDefaultDomain());
119 this.transactionsMBeanServer = MBeanServerFactory
120 .createMBeanServer("ConfigTransactions" + configMBeanServer.getDefaultDomain());
124 * Create new {@link ConfigTransactionControllerImpl }
127 public synchronized ObjectName beginConfig() {
128 return beginConfigInternal().getControllerObjectName();
131 private synchronized ConfigTransactionControllerInternal beginConfigInternal() {
133 String transactionName = "ConfigTransaction-" + version + "-"
135 TransactionJMXRegistrator transactionRegistrator = baseJMXRegistrator
136 .createTransactionJMXRegistrator(transactionName);
137 ConfigTransactionControllerInternal transactionController = new ConfigTransactionControllerImpl(
138 transactionName, transactionRegistrator, version,
139 versionCounter, resolver.getAllFactories(),
140 transactionsMBeanServer, configMBeanServer);
142 transactionRegistrator.registerMBean(transactionController, transactionController.getControllerObjectName
144 } catch (InstanceAlreadyExistsException e) {
145 throw new IllegalStateException(e);
148 // copy old configuration to this server
149 for (ModuleInternalInfo oldConfigInfo : currentConfig.getEntries()) {
151 transactionController.copyExistingModule(oldConfigInfo);
152 } catch (InstanceAlreadyExistsException e) {
153 throw new IllegalStateException("Error while copying "
157 transactionsHolder.add(transactionName, transactionController);
158 return transactionController;
165 public synchronized CommitStatus commitConfig(
166 ObjectName transactionControllerON)
167 throws ConflictingVersionException, ValidationException {
168 final String transactionName = ObjectNameUtil
169 .getTransactionName(transactionControllerON);
171 "About to commit {}. Current parentVersion: {}, versionCounter {}",
172 transactionName, version, versionCounter);
174 // find ConfigTransactionController
175 Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder
176 .getCurrentTransactions();
177 ConfigTransactionControllerInternal configTransactionController = transactions
178 .get(transactionName);
179 if (configTransactionController == null) {
180 throw new IllegalArgumentException(String.format(
181 "Transaction with name '%s' not found", transactionName));
183 // check optimistic lock
184 if (version != configTransactionController.getParentVersion()) {
185 throw new ConflictingVersionException(
187 "Optimistic lock failed. Expected parent version %d, was %d",
189 configTransactionController.getParentVersion()));
191 // optimistic lock ok
193 CommitInfo commitInfo = configTransactionController
194 .validateBeforeCommitAndLockTransaction();
195 final ConfigRegistryImpl a = this;
196 // non recoverable from here:
198 final CommitStatus secondPhaseCommitStatus = secondPhaseCommit(
199 configTransactionController, commitInfo);
201 return secondPhaseCommitStatus;
202 } catch (Throwable t) { // some libs throw Errors: e.g.
203 // javax.xml.ws.spi.FactoryFinder$ConfigurationError
206 "Configuration Transaction failed on 2PC, server is unhealthy",
208 if (t instanceof RuntimeException)
209 throw (RuntimeException) t;
210 else if (t instanceof Error)
213 throw new RuntimeException(t);
217 private CommitStatus secondPhaseCommit(
218 ConfigTransactionControllerInternal configTransactionController,
219 CommitInfo commitInfo) {
221 // close instances which were destroyed by the user, including
222 // (hopefully) runtime beans
223 for (DestroyedModule toBeDestroyed : commitInfo
224 .getDestroyedFromPreviousTransactions()) {
225 toBeDestroyed.close(); // closes instance (which should close
226 // runtime jmx registrator),
227 // also closes osgi registration and ModuleJMXRegistrator
229 currentConfig.remove(toBeDestroyed.getName());
232 // set RuntimeBeanRegistrators on beans implementing
233 // RuntimeBeanRegistratorAwareModule, each module
234 // should have exactly one runtime jmx registrator.
235 Map<ModuleIdentifier, RootRuntimeBeanRegistratorImpl> runtimeRegistrators = new HashMap<>();
236 for (ModuleInternalTransactionalInfo entry : commitInfo.getCommitted()
238 RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator;
239 if (entry.hasOldModule() == false) {
240 runtimeBeanRegistrator = baseJMXRegistrator
241 .createRuntimeBeanRegistrator(entry.getName());
243 // reuse old JMX registrator
244 runtimeBeanRegistrator = entry.getOldInternalInfo()
245 .getRuntimeBeanRegistrator();
247 // set runtime jmx registrator if required
248 Module module = entry.getModule();
249 if (module instanceof RuntimeBeanRegistratorAwareModule) {
250 ((RuntimeBeanRegistratorAwareModule) module)
251 .setRuntimeBeanRegistrator(runtimeBeanRegistrator);
253 // save it to info so it is accessible afterwards
254 runtimeRegistrators.put(entry.getName(), runtimeBeanRegistrator);
257 // can register runtime beans
258 List<ModuleIdentifier> orderedModuleIdentifiers = configTransactionController
259 .secondPhaseCommit();
261 // copy configuration to read only mode
262 List<ObjectName> newInstances = new LinkedList<>();
263 List<ObjectName> reusedInstances = new LinkedList<>();
264 List<ObjectName> recreatedInstances = new LinkedList<>();
266 Map<Module, ModuleInternalInfo> newConfigEntries = new HashMap<>();
269 for (ModuleIdentifier moduleIdentifier : orderedModuleIdentifiers) {
270 ModuleInternalTransactionalInfo entry = commitInfo.getCommitted()
271 .get(moduleIdentifier);
273 throw new NullPointerException("Module not found "
275 Module module = entry.getModule();
276 ObjectName primaryReadOnlyON = ObjectNameUtil
277 .createReadOnlyModuleON(moduleIdentifier);
279 // determine if current instance was recreated or reused or is new
281 // rules for closing resources:
282 // osgi registration - will be (re)created every time, so it needs
284 // module jmx registration - will be (re)created every time, needs
286 // runtime jmx registration - should be taken care of by module
288 // instance - is closed only if it was destroyed
289 ModuleJMXRegistrator newModuleJMXRegistrator = baseJMXRegistrator
290 .createModuleJMXRegistrator();
292 if (entry.hasOldModule()) {
293 ModuleInternalInfo oldInternalInfo = entry.getOldInternalInfo();
294 DynamicReadableWrapper oldReadableConfigBean = oldInternalInfo
295 .getReadableModule();
296 currentConfig.remove(entry.getName());
298 // test if old instance == new instance
299 if (oldReadableConfigBean.getInstance().equals(
300 module.getInstance())) {
301 // reused old instance:
302 // wrap in readable dynamic mbean
303 reusedInstances.add(primaryReadOnlyON);
305 // recreated instance:
306 // it is responsibility of module to call the old instance -
307 // we just need to unregister configbean
308 recreatedInstances.add(primaryReadOnlyON);
310 // close old osgi registration in any case
311 oldInternalInfo.getOsgiRegistration().close();
312 // close old module jmx registrator
313 oldInternalInfo.getModuleJMXRegistrator().close();
316 // wrap in readable dynamic mbean
317 newInstances.add(primaryReadOnlyON);
320 DynamicReadableWrapper newReadableConfigBean = new DynamicReadableWrapper(
321 module, module.getInstance(), moduleIdentifier,
322 registryMBeanServer, configMBeanServer);
326 newModuleJMXRegistrator.registerMBean(newReadableConfigBean,
328 } catch (InstanceAlreadyExistsException e) {
329 throw new IllegalStateException(e);
333 OsgiRegistration osgiRegistration = beanToOsgiServiceManager
334 .registerToOsgi(module.getClass(),
335 newReadableConfigBean.getInstance(),
338 RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator = runtimeRegistrators
339 .get(entry.getName());
340 ModuleInternalInfo newInfo = new ModuleInternalInfo(
341 entry.getName(), newReadableConfigBean, osgiRegistration,
342 runtimeBeanRegistrator, newModuleJMXRegistrator,
345 newConfigEntries.put(module, newInfo);
348 currentConfig.addAll(newConfigEntries.values());
351 version = configTransactionController.getVersion();
352 return new CommitStatus(newInstances, reusedInstances,
360 public synchronized List<ObjectName> getOpenConfigs() {
361 Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder
362 .getCurrentTransactions();
363 List<ObjectName> result = new ArrayList<>(transactions.size());
364 for (ConfigTransactionControllerInternal configTransactionController : transactions
366 result.add(configTransactionController.getControllerObjectName());
372 * Abort open transactions and unregister read only modules. Since this
373 * class is not responsible for registering itself under
374 * {@link ConfigRegistryMXBean#OBJECT_NAME}, it will not unregister itself
378 public synchronized void close() {
379 // abort transactions
380 Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder
381 .getCurrentTransactions();
382 for (ConfigTransactionControllerInternal configTransactionController : transactions
385 configTransactionController.abortConfig();
386 } catch (RuntimeException e) {
387 logger.warn("Ignoring exception while aborting {}",
388 configTransactionController, e);
392 // destroy all live objects one after another in order of the dependency
394 List<DestroyedModule> destroyedModules = currentConfig
395 .getModulesToBeDestroyed();
396 for (DestroyedModule destroyedModule : destroyedModules) {
397 destroyedModule.close();
399 // unregister MBeans that failed to unregister properly
400 baseJMXRegistrator.close();
401 // remove jmx servers
402 MBeanServerFactory.releaseMBeanServer(registryMBeanServer);
403 MBeanServerFactory.releaseMBeanServer(transactionsMBeanServer);
411 public long getVersion() {
419 public Set<String> getAvailableModuleNames() {
420 return new HierarchicalConfigMBeanFactoriesHolder(
421 resolver.getAllFactories()).getModuleNames();
428 public boolean isHealthy() {
438 public Set<ObjectName> lookupConfigBeans() {
439 return lookupConfigBeans("*", "*");
446 public Set<ObjectName> lookupConfigBeans(String moduleName) {
447 return lookupConfigBeans(moduleName, "*");
454 public ObjectName lookupConfigBean(String moduleName, String instanceName)
455 throws InstanceNotFoundException {
456 return LookupBeansUtil.lookupConfigBean(this, moduleName, instanceName);
463 public Set<ObjectName> lookupConfigBeans(String moduleName,
464 String instanceName) {
465 ObjectName namePattern = ObjectNameUtil.createModulePattern(moduleName,
467 return baseJMXRegistrator.queryNames(namePattern, null);
474 public Set<ObjectName> lookupRuntimeBeans() {
475 return lookupRuntimeBeans("*", "*");
482 public Set<ObjectName> lookupRuntimeBeans(String moduleName,
483 String instanceName) {
484 if (moduleName == null)
486 if (instanceName == null)
488 ObjectName namePattern = ObjectNameUtil.createRuntimeBeanPattern(
489 moduleName, instanceName);
490 return baseJMXRegistrator.queryNames(namePattern, null);
496 * Holds currently running modules
500 private final Map<ModuleIdentifier, ModuleInternalInfo> currentConfig = new HashMap<>();
503 * Add all modules to the internal map. Also add service instance to OSGi
506 public void addAll(Collection<ModuleInternalInfo> configInfos) {
507 if (currentConfig.size() > 0) {
508 throw new IllegalStateException(
509 "Error - some config entries were not removed: "
512 for (ModuleInternalInfo configInfo : configInfos) {
517 private void add(ModuleInternalInfo configInfo) {
518 ModuleInternalInfo oldValue = currentConfig.put(configInfo.getName(),
520 if (oldValue != null) {
521 throw new IllegalStateException(
522 "Cannot overwrite module with same name:"
523 + configInfo.getName() + ":" + configInfo);
528 * Remove entry from current config.
530 public void remove(ModuleIdentifier name) {
531 ModuleInternalInfo removed = currentConfig.remove(name);
532 if (removed == null) {
533 throw new IllegalStateException(
534 "Cannot remove from ConfigHolder - name not found:" + name);
538 public Collection<ModuleInternalInfo> getEntries() {
539 return currentConfig.values();
542 public List<DestroyedModule> getModulesToBeDestroyed() {
543 List<DestroyedModule> result = new ArrayList<>();
544 for (ModuleInternalInfo moduleInternalInfo : getEntries()) {
545 result.add(moduleInternalInfo.toDestroyedModule());
547 Collections.sort(result);
553 * Holds Map<transactionName, transactionController> and purges it each time its
554 * content is requested.
557 class TransactionsHolder {
559 * This map keeps transaction names and
560 * {@link ConfigTransactionControllerInternal} instances, because platform
561 * MBeanServer transforms mbeans into another representation. Map is cleaned
562 * every time current transactions are requested.
565 @GuardedBy("ConfigRegistryImpl.this")
566 private final Map<String /* transactionName */, ConfigTransactionControllerInternal> transactions = new HashMap<>();
569 * Can only be called from within synchronized method.
571 public void add(String transactionName,
572 ConfigTransactionControllerInternal transactionController) {
573 Object oldValue = transactions.put(transactionName,
574 transactionController);
575 if (oldValue != null) {
576 throw new IllegalStateException(
577 "Error: two transactions with same name");
582 * Purges closed transactions from transactions map. Can only be called from
583 * within synchronized method. Calling this method more than once within the
584 * method can modify the resulting map that was obtained in previous calls.
586 * @return current view on transactions map.
588 public Map<String, ConfigTransactionControllerInternal> getCurrentTransactions() {
589 // first, remove closed transaction
590 for (Iterator<Entry<String, ConfigTransactionControllerInternal>> it = transactions
591 .entrySet().iterator(); it.hasNext();) {
592 Entry<String, ConfigTransactionControllerInternal> entry = it
594 if (entry.getValue().isClosed()) {
598 return Collections.unmodifiableMap(transactions);