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 static com.google.common.base.Preconditions.checkNotNull;
11 import static java.lang.String.format;
13 import com.google.common.collect.Lists;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.HashSet;
17 import java.util.List;
19 import java.util.Map.Entry;
21 import java.util.concurrent.atomic.AtomicBoolean;
22 import javax.annotation.Nullable;
23 import javax.annotation.concurrent.GuardedBy;
24 import javax.management.DynamicMBean;
25 import javax.management.InstanceAlreadyExistsException;
26 import javax.management.InstanceNotFoundException;
27 import javax.management.MBeanServer;
28 import javax.management.ObjectName;
29 import org.opendaylight.controller.config.api.DependencyResolver;
30 import org.opendaylight.controller.config.api.ModuleIdentifier;
31 import org.opendaylight.controller.config.api.ValidationException;
32 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
33 import org.opendaylight.controller.config.manager.impl.dependencyresolver.DependencyResolverManager;
34 import org.opendaylight.controller.config.manager.impl.dependencyresolver.ModuleInternalTransactionalInfo;
35 import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicWritableWrapper;
36 import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean;
37 import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean.ReadOnlyAtomicBooleanImpl;
38 import org.opendaylight.controller.config.manager.impl.factoriesresolver.HierarchicalConfigMBeanFactoriesHolder;
39 import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator;
40 import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator.TransactionModuleJMXRegistration;
41 import org.opendaylight.controller.config.spi.Module;
42 import org.opendaylight.controller.config.spi.ModuleFactory;
43 import org.opendaylight.yangtools.concepts.Identifiable;
44 import org.opendaylight.yangtools.yang.data.impl.codec.CodecRegistry;
45 import org.osgi.framework.BundleContext;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
49 * This is a JMX bean representing current transaction. It contains
50 * transaction identifier, unique version and parent version for
53 class ConfigTransactionControllerImpl implements
54 ConfigTransactionControllerInternal,
55 ConfigTransactionControllerImplMXBean,
56 Identifiable<TransactionIdentifier> {
57 private static final Logger LOG = LoggerFactory.getLogger(ConfigTransactionControllerImpl.class);
59 private final ConfigTransactionLookupRegistry txLookupRegistry;
60 private final ObjectName controllerON;
62 private final long parentVersion, currentVersion;
63 private final HierarchicalConfigMBeanFactoriesHolder factoriesHolder;
64 private final DependencyResolverManager dependencyResolverManager;
65 private final TransactionStatus transactionStatus;
66 private final MBeanServer transactionsMBeanServer;
67 private final Map<String, Map.Entry<ModuleFactory, BundleContext>> currentlyRegisteredFactories;
70 * Disables ability of {@link DynamicWritableWrapper} to change attributes
74 private final AtomicBoolean configBeanModificationDisabled = new AtomicBoolean(
76 private final ReadOnlyAtomicBoolean readOnlyAtomicBoolean = new ReadOnlyAtomicBooleanImpl(
77 configBeanModificationDisabled);
78 private final MBeanServer configMBeanServer;
80 private final boolean blankTransaction;
83 private final SearchableServiceReferenceWritableRegistry writableSRRegistry;
85 public ConfigTransactionControllerImpl(ConfigTransactionLookupRegistry txLookupRegistry,
86 long parentVersion, CodecRegistry codecRegistry, long currentVersion,
87 Map<String, Entry<ModuleFactory, BundleContext>> currentlyRegisteredFactories,
88 MBeanServer transactionsMBeanServer, MBeanServer configMBeanServer,
89 boolean blankTransaction, SearchableServiceReferenceWritableRegistry writableSRRegistry) {
90 this.txLookupRegistry = txLookupRegistry;
91 String transactionName = txLookupRegistry.getTransactionIdentifier().getName();
92 this.controllerON = ObjectNameUtil.createTransactionControllerON(transactionName);
93 this.parentVersion = parentVersion;
94 this.currentVersion = currentVersion;
95 this.currentlyRegisteredFactories = currentlyRegisteredFactories;
96 this.factoriesHolder = new HierarchicalConfigMBeanFactoriesHolder(currentlyRegisteredFactories);
97 this.transactionStatus = new TransactionStatus();
98 this.dependencyResolverManager = new DependencyResolverManager(txLookupRegistry.getTransactionIdentifier(),
99 transactionStatus, writableSRRegistry, codecRegistry, transactionsMBeanServer);
100 this.transactionsMBeanServer = transactionsMBeanServer;
101 this.configMBeanServer = configMBeanServer;
102 this.blankTransaction = blankTransaction;
103 this.writableSRRegistry = writableSRRegistry;
107 public void copyExistingModulesAndProcessFactoryDiff(Collection<ModuleInternalInfo> existingModules, List<ModuleFactory> lastListOfFactories) {
108 // copy old configuration to this server
109 for (ModuleInternalInfo oldConfigInfo : existingModules) {
111 copyExistingModule(oldConfigInfo);
112 } catch (InstanceAlreadyExistsException e) {
113 throw new IllegalStateException("Error while copying " + oldConfigInfo, e);
116 processDefaultBeans(lastListOfFactories);
119 private synchronized void processDefaultBeans(List<ModuleFactory> lastListOfFactories) {
120 transactionStatus.checkNotCommitStarted();
121 transactionStatus.checkNotAborted();
123 Set<ModuleFactory> oldSet = new HashSet<>(lastListOfFactories);
124 Set<ModuleFactory> newSet = new HashSet<>(factoriesHolder.getModuleFactories());
126 List<ModuleFactory> toBeAdded = new ArrayList<>();
127 List<ModuleFactory> toBeRemoved = new ArrayList<>();
128 for (ModuleFactory moduleFactory : factoriesHolder.getModuleFactories()) {
129 if (oldSet.contains(moduleFactory) == false) {
130 toBeAdded.add(moduleFactory);
133 for (ModuleFactory moduleFactory : lastListOfFactories) {
134 if (newSet.contains(moduleFactory) == false) {
135 toBeRemoved.add(moduleFactory);
138 // add default modules
139 for (ModuleFactory moduleFactory : toBeAdded) {
140 BundleContext bundleContext = getModuleFactoryBundleContext(moduleFactory.getImplementationName());
141 Set<? extends Module> defaultModules = moduleFactory.getDefaultModules(dependencyResolverManager,
143 for (Module module : defaultModules) {
144 // ensure default module to be registered to jmx even if its module factory does not use dependencyResolverFactory
145 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(module.getIdentifier());
147 boolean defaultBean = true;
148 putConfigBeanToJMXAndInternalMaps(module.getIdentifier(), module, moduleFactory, null,
149 dependencyResolver, defaultBean, bundleContext);
150 } catch (InstanceAlreadyExistsException e) {
151 throw new IllegalStateException(e);
156 // remove modules belonging to removed factories
157 for (ModuleFactory removedFactory : toBeRemoved) {
158 List<ModuleIdentifier> modulesOfRemovedFactory = dependencyResolverManager.findAllByFactory(removedFactory);
159 for (ModuleIdentifier name : modulesOfRemovedFactory) {
166 private synchronized void copyExistingModule(ModuleInternalInfo oldConfigBeanInfo) throws InstanceAlreadyExistsException {
168 transactionStatus.checkNotCommitStarted();
169 transactionStatus.checkNotAborted();
170 ModuleIdentifier moduleIdentifier = oldConfigBeanInfo.getIdentifier();
171 dependencyResolverManager.assertNotExists(moduleIdentifier);
173 ModuleFactory moduleFactory;
176 moduleFactory = factoriesHolder.findByModuleName(moduleIdentifier.getFactoryName());
177 bc = getModuleFactoryBundleContext(moduleFactory.getImplementationName());
178 } catch (InstanceNotFoundException e) {
179 throw new IllegalStateException(e);
183 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(moduleIdentifier);
186 module = moduleFactory.createModule(
187 moduleIdentifier.getInstanceName(), dependencyResolver,
188 oldConfigBeanInfo.getReadableModule(), bc);
189 } catch (Exception e) {
190 throw new IllegalStateException(format(
191 "Error while copying old configuration from %s to %s",
192 oldConfigBeanInfo, moduleFactory), e);
194 putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module, moduleFactory, oldConfigBeanInfo, dependencyResolver,
195 oldConfigBeanInfo.isDefaultBean(), bc);
199 public synchronized ObjectName createModule(String factoryName,
200 String instanceName) throws InstanceAlreadyExistsException {
202 transactionStatus.checkNotCommitStarted();
203 transactionStatus.checkNotAborted();
204 ModuleIdentifier moduleIdentifier = new ModuleIdentifier(factoryName, instanceName);
205 dependencyResolverManager.assertNotExists(moduleIdentifier);
208 ModuleFactory moduleFactory;
210 moduleFactory = factoriesHolder.findByModuleName(factoryName);
211 } catch (InstanceNotFoundException e) {
212 throw new IllegalArgumentException(e);
214 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(moduleIdentifier);
215 BundleContext bundleContext = getModuleFactoryBundleContext(moduleFactory.getImplementationName());
216 Module module = moduleFactory.createModule(instanceName, dependencyResolver,
218 boolean defaultBean = false;
219 return putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module,
220 moduleFactory, null, dependencyResolver, defaultBean, bundleContext);
223 private synchronized ObjectName putConfigBeanToJMXAndInternalMaps(
224 ModuleIdentifier moduleIdentifier, Module module,
225 ModuleFactory moduleFactory,
226 @Nullable ModuleInternalInfo maybeOldConfigBeanInfo, DependencyResolver dependencyResolver,
227 boolean isDefaultBean, BundleContext bundleContext)
228 throws InstanceAlreadyExistsException {
230 LOG.debug("Adding module {} to transaction {}", moduleIdentifier, this);
231 if (moduleIdentifier.equals(module.getIdentifier()) == false) {
232 throw new IllegalStateException("Incorrect name reported by module. Expected "
233 + moduleIdentifier + ", got " + module.getIdentifier());
235 if (dependencyResolver.getIdentifier().equals(moduleIdentifier) == false) {
236 throw new IllegalStateException("Incorrect name reported by dependency resolver. Expected "
237 + moduleIdentifier + ", got " + dependencyResolver.getIdentifier());
239 DynamicMBean writableDynamicWrapper = new DynamicWritableWrapper(
240 module, moduleIdentifier, getTransactionIdentifier(),
241 readOnlyAtomicBoolean, transactionsMBeanServer,
244 ObjectName writableON = ObjectNameUtil.createTransactionModuleON(
245 getTransactionIdentifier().getName(), moduleIdentifier);
246 // put wrapper to jmx
247 TransactionModuleJMXRegistration transactionModuleJMXRegistration = getTxModuleJMXRegistrator()
248 .registerMBean(writableDynamicWrapper, writableON);
250 dependencyResolverManager.put(
251 moduleIdentifier, module, moduleFactory,
252 maybeOldConfigBeanInfo, transactionModuleJMXRegistration, isDefaultBean, bundleContext);
257 public synchronized void destroyModule(ObjectName objectName) throws InstanceNotFoundException {
258 checkTransactionName(objectName);
259 ObjectNameUtil.checkDomain(objectName);
260 ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(objectName,
261 ObjectNameUtil.TYPE_MODULE);
262 destroyModule(moduleIdentifier);
265 private void checkTransactionName(ObjectName objectName) {
266 String foundTransactionName = ObjectNameUtil
267 .getTransactionName(objectName);
268 if (getTransactionIdentifier().getName().equals(foundTransactionName) == false) {
269 throw new IllegalArgumentException("Wrong transaction name "
274 private synchronized void destroyModule(ModuleIdentifier moduleIdentifier) {
275 LOG.debug("Destroying module {} in transaction {}", moduleIdentifier, this);
276 transactionStatus.checkNotAborted();
278 ModuleInternalTransactionalInfo found = dependencyResolverManager.findModuleInternalTransactionalInfo(moduleIdentifier);
279 if (blankTransaction == false &&
280 found.isDefaultBean()) {
281 LOG.warn("Warning: removing default bean. This will be forbidden in next version of config-subsystem");
283 // first remove refNames, it checks for objectname existence
286 writableSRRegistry.removeServiceReferences(
287 ObjectNameUtil.createTransactionModuleON(getTransactionName(), moduleIdentifier));
288 } catch (InstanceNotFoundException e) {
289 LOG.error("Possible code error: cannot find {} in {}", moduleIdentifier, writableSRRegistry);
290 throw new IllegalStateException("Possible code error: cannot find " + moduleIdentifier, e);
293 ModuleInternalTransactionalInfo removedTInfo = dependencyResolverManager.destroyModule(moduleIdentifier);
295 removedTInfo.getTransactionModuleJMXRegistration().close();
299 public long getParentVersion() {
300 return parentVersion;
304 public long getVersion() {
305 return currentVersion;
309 public synchronized void validateConfig() throws ValidationException {
310 if (configBeanModificationDisabled.get()) {
311 throw new IllegalStateException("Cannot start validation");
313 configBeanModificationDisabled.set(true);
317 configBeanModificationDisabled.set(false);
321 private void validateNoLocks() throws ValidationException {
322 transactionStatus.checkNotAborted();
323 LOG.trace("Validating transaction {}", getTransactionIdentifier());
325 List<ValidationException> collectedExceptions = new ArrayList<>();
326 for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
327 .getAllModules().entrySet()) {
328 ModuleIdentifier name = entry.getKey();
329 Module module = entry.getValue();
332 } catch (Exception e) {
333 LOG.warn("Validation exception in {}", getTransactionName(),
335 collectedExceptions.add(ValidationException
336 .createForSingleException(name, e));
339 if (!collectedExceptions.isEmpty()) {
340 throw ValidationException
341 .createFromCollectedValidationExceptions(collectedExceptions);
343 LOG.trace("Validated transaction {}", getTransactionIdentifier());
347 * If this method passes validation, it will grab
348 * {@link TransactionStatus#secondPhaseCommitStarted} lock. This lock will
349 * prevent calling @{link #validateBeforeCommitAndLockTransaction},
350 * effectively only allowing to call {@link #secondPhaseCommit} after
351 * successful return of this method.
354 public synchronized CommitInfo validateBeforeCommitAndLockTransaction()
355 throws ValidationException {
356 transactionStatus.checkNotAborted();
357 transactionStatus.checkNotCommitStarted();
358 configBeanModificationDisabled.set(true);
361 } catch (ValidationException e) {
362 LOG.trace("Commit failed on validation");
363 configBeanModificationDisabled.set(false); // recoverable error
366 // errors in this state are not recoverable. modules are not mutable
368 transactionStatus.setSecondPhaseCommitStarted();
369 return dependencyResolverManager.toCommitInfo();
376 public synchronized List<ModuleIdentifier> secondPhaseCommit() {
377 transactionStatus.checkNotAborted();
378 transactionStatus.checkCommitStarted();
379 if (configBeanModificationDisabled.get() == false) {
380 throw new IllegalStateException(
381 "Internal error - validateBeforeCommitAndLockTransaction should be called "
382 + "to obtain a lock");
385 LOG.trace("Committing transaction {}", getTransactionIdentifier());
387 Map<ModuleIdentifier, Module> allModules = dependencyResolverManager.getAllModules();
389 // call getInstance() on all Modules from top to bottom (from source to target of the dependency relation)
390 // The source of a dependency closes itself and calls getInstance recursively on the dependencies (in case of reconfiguration)
391 // This makes close() calls from top to bottom while createInstance() calls are performed bottom to top
392 List<ModuleIdentifier> sortedModuleIdentifiers = Lists.reverse(dependencyResolverManager.getSortedModuleIdentifiers());
393 for (ModuleIdentifier moduleIdentifier : sortedModuleIdentifiers) {
394 Module module = allModules.get(moduleIdentifier);
397 LOG.debug("About to commit {} in transaction {}",
398 moduleIdentifier, getTransactionIdentifier());
399 AutoCloseable instance = module.getInstance();
400 checkNotNull(instance, "Instance is null:{} in transaction {}", moduleIdentifier, getTransactionIdentifier());
401 } catch (Exception e) {
402 LOG.error("Commit failed on {} in transaction {}", moduleIdentifier,
403 getTransactionIdentifier(), e);
405 throw new IllegalStateException(
406 format("Error - getInstance() failed for %s in transaction %s",
407 moduleIdentifier, getTransactionIdentifier()), e);
411 LOG.trace("Committed configuration {}", getTransactionIdentifier());
412 transactionStatus.setCommitted();
414 return sortedModuleIdentifiers;
418 public synchronized void abortConfig() {
419 transactionStatus.checkNotCommitStarted();
420 transactionStatus.checkNotAborted();
424 private void internalAbort() {
425 LOG.trace("Aborting {}", this);
426 transactionStatus.setAborted();
430 public void close() {
431 dependencyResolverManager.close();
432 txLookupRegistry.close();
436 public ObjectName getControllerObjectName() {
441 public String getTransactionName() {
442 return getTransactionIdentifier().getName();
449 public Set<ObjectName> lookupConfigBeans() {
450 return txLookupRegistry.lookupConfigBeans();
457 public Set<ObjectName> lookupConfigBeans(String moduleName) {
458 return txLookupRegistry.lookupConfigBeans(moduleName);
465 public ObjectName lookupConfigBean(String moduleName, String instanceName)
466 throws InstanceNotFoundException {
467 return txLookupRegistry.lookupConfigBean(moduleName, instanceName);
474 public Set<ObjectName> lookupConfigBeans(String moduleName, String instanceName) {
475 return txLookupRegistry.lookupConfigBeans(moduleName, instanceName);
482 public void checkConfigBeanExists(ObjectName objectName) throws InstanceNotFoundException {
483 txLookupRegistry.checkConfigBeanExists(objectName);
491 public Set<ObjectName> lookupRuntimeBeans() {
492 return txLookupRegistry.lookupRuntimeBeans();
499 public Set<ObjectName> lookupRuntimeBeans(String moduleName,
500 String instanceName) {
501 return txLookupRegistry.lookupRuntimeBeans(moduleName, instanceName);
510 public Set<String> getAvailableModuleNames() {
511 return factoriesHolder.getModuleNames();
515 public boolean isClosed() {
516 return transactionStatus.isAbortedOrCommitted();
520 public String toString() {
521 StringBuilder sb = new StringBuilder();
522 sb.append("transactionName=");
523 sb.append(getTransactionName());
524 return sb.toString();
527 // @VisibleForTesting
529 TransactionModuleJMXRegistrator getTxModuleJMXRegistrator() {
530 return txLookupRegistry.getTxModuleJMXRegistrator();
533 public TransactionIdentifier getName() {
534 return getTransactionIdentifier();
538 public List<ModuleFactory> getCurrentlyRegisteredFactories() {
539 return new ArrayList<>(factoriesHolder.getModuleFactories());
543 public TransactionIdentifier getIdentifier() {
544 return getTransactionIdentifier();
548 public BundleContext getModuleFactoryBundleContext(String factoryName) {
549 Map.Entry<ModuleFactory, BundleContext> factoryBundleContextEntry = this.currentlyRegisteredFactories.get(factoryName);
550 if (factoryBundleContextEntry == null || factoryBundleContextEntry.getValue() == null) {
551 throw new NullPointerException("Bundle context of " + factoryName + " ModuleFactory not found.");
553 return factoryBundleContextEntry.getValue();
556 // service reference functionality:
560 public synchronized ObjectName lookupConfigBeanByServiceInterfaceName(String serviceInterfaceQName, String refName) {
561 return writableSRRegistry.lookupConfigBeanByServiceInterfaceName(serviceInterfaceQName, refName);
565 public synchronized Map<String, Map<String, ObjectName>> getServiceMapping() {
566 return writableSRRegistry.getServiceMapping();
570 public synchronized Map<String, ObjectName> lookupServiceReferencesByServiceInterfaceName(String serviceInterfaceQName) {
571 return writableSRRegistry.lookupServiceReferencesByServiceInterfaceName(serviceInterfaceQName);
575 public synchronized Set<String> lookupServiceInterfaceNames(ObjectName objectName) throws InstanceNotFoundException {
576 return writableSRRegistry.lookupServiceInterfaceNames(objectName);
580 public synchronized String getServiceInterfaceName(String namespace, String localName) {
581 return writableSRRegistry.getServiceInterfaceName(namespace, localName);
585 public synchronized ObjectName saveServiceReference(String serviceInterfaceName, String refName, ObjectName moduleON) throws InstanceNotFoundException {
586 return writableSRRegistry.saveServiceReference(serviceInterfaceName, refName, moduleON);
590 public synchronized void removeServiceReference(String serviceInterfaceName, String refName) throws InstanceNotFoundException {
591 writableSRRegistry.removeServiceReference(serviceInterfaceName, refName);
595 public synchronized void removeAllServiceReferences() {
596 writableSRRegistry.removeAllServiceReferences();
600 public boolean removeServiceReferences(ObjectName objectName) throws InstanceNotFoundException {
601 return writableSRRegistry.removeServiceReferences(objectName);
605 public SearchableServiceReferenceWritableRegistry getWritableRegistry() {
606 return writableSRRegistry;
610 public TransactionIdentifier getTransactionIdentifier() {
611 return txLookupRegistry.getTransactionIdentifier();
615 public Set<String> getAvailableModuleFactoryQNames() {
616 return txLookupRegistry.getAvailableModuleFactoryQNames();
620 public void checkServiceReferenceExists(ObjectName objectName) throws InstanceNotFoundException {
621 writableSRRegistry.checkServiceReferenceExists(objectName);
625 public ObjectName getServiceReference(String serviceInterfaceQName, String refName) throws InstanceNotFoundException {
626 return writableSRRegistry.getServiceReference(serviceInterfaceQName, refName);