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.annotations.ServiceInterfaceAnnotation;
33 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
34 import org.opendaylight.controller.config.manager.impl.dependencyresolver.DependencyResolverManager;
35 import org.opendaylight.controller.config.manager.impl.dependencyresolver.ModuleInternalTransactionalInfo;
36 import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicWritableWrapper;
37 import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean;
38 import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean.ReadOnlyAtomicBooleanImpl;
39 import org.opendaylight.controller.config.manager.impl.factoriesresolver.HierarchicalConfigMBeanFactoriesHolder;
40 import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator;
41 import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator.TransactionModuleJMXRegistration;
42 import org.opendaylight.controller.config.manager.impl.osgi.mapping.BindingContextProvider;
43 import org.opendaylight.controller.config.manager.impl.util.InterfacesHelper;
44 import org.opendaylight.controller.config.spi.Module;
45 import org.opendaylight.controller.config.spi.ModuleFactory;
46 import org.opendaylight.yangtools.concepts.Identifiable;
47 import org.osgi.framework.BundleContext;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
51 * This is a JMX bean representing current transaction. It contains
52 * transaction identifier, unique version and parent version for
55 class ConfigTransactionControllerImpl implements
56 ConfigTransactionControllerInternal,
57 ConfigTransactionControllerImplMXBean,
58 Identifiable<TransactionIdentifier> {
59 private static final Logger LOG = LoggerFactory.getLogger(ConfigTransactionControllerImpl.class);
61 private final ConfigTransactionLookupRegistry txLookupRegistry;
62 private final ObjectName controllerON;
64 private final long parentVersion, currentVersion;
65 private final HierarchicalConfigMBeanFactoriesHolder factoriesHolder;
66 private final DependencyResolverManager dependencyResolverManager;
67 private final TransactionStatus transactionStatus;
68 private final MBeanServer transactionsMBeanServer;
69 private final Map<String, Map.Entry<ModuleFactory, BundleContext>> currentlyRegisteredFactories;
72 * Disables ability of {@link DynamicWritableWrapper} to change attributes
76 private final AtomicBoolean configBeanModificationDisabled = new AtomicBoolean(
78 private final ReadOnlyAtomicBoolean readOnlyAtomicBoolean = new ReadOnlyAtomicBooleanImpl(
79 configBeanModificationDisabled);
80 private final MBeanServer configMBeanServer;
82 private final boolean blankTransaction;
85 private final SearchableServiceReferenceWritableRegistry writableSRRegistry;
87 public ConfigTransactionControllerImpl(ConfigTransactionLookupRegistry txLookupRegistry,
88 long parentVersion, BindingContextProvider bindingContextProvider, long currentVersion,
89 Map<String, Entry<ModuleFactory, BundleContext>> currentlyRegisteredFactories,
90 MBeanServer transactionsMBeanServer, MBeanServer configMBeanServer,
91 boolean blankTransaction, SearchableServiceReferenceWritableRegistry writableSRRegistry) {
92 this.txLookupRegistry = txLookupRegistry;
93 String transactionName = txLookupRegistry.getTransactionIdentifier().getName();
94 this.controllerON = ObjectNameUtil.createTransactionControllerON(transactionName);
95 this.parentVersion = parentVersion;
96 this.currentVersion = currentVersion;
97 this.currentlyRegisteredFactories = currentlyRegisteredFactories;
98 this.factoriesHolder = new HierarchicalConfigMBeanFactoriesHolder(currentlyRegisteredFactories);
99 this.transactionStatus = new TransactionStatus();
100 this.dependencyResolverManager = new DependencyResolverManager(txLookupRegistry.getTransactionIdentifier(),
101 transactionStatus, writableSRRegistry, bindingContextProvider, transactionsMBeanServer);
102 this.transactionsMBeanServer = transactionsMBeanServer;
103 this.configMBeanServer = configMBeanServer;
104 this.blankTransaction = blankTransaction;
105 this.writableSRRegistry = writableSRRegistry;
109 public void copyExistingModulesAndProcessFactoryDiff(Collection<ModuleInternalInfo> existingModules, List<ModuleFactory> lastListOfFactories) {
110 // copy old configuration to this server
111 for (ModuleInternalInfo oldConfigInfo : existingModules) {
113 copyExistingModule(oldConfigInfo);
114 } catch (InstanceAlreadyExistsException e) {
115 throw new IllegalStateException("Error while copying " + oldConfigInfo, e);
118 processDefaultBeans(lastListOfFactories);
121 private synchronized void processDefaultBeans(List<ModuleFactory> lastListOfFactories) {
122 transactionStatus.checkNotCommitStarted();
123 transactionStatus.checkNotAborted();
125 Set<ModuleFactory> oldSet = new HashSet<>(lastListOfFactories);
126 Set<ModuleFactory> newSet = new HashSet<>(factoriesHolder.getModuleFactories());
128 List<ModuleFactory> toBeAdded = new ArrayList<>();
129 List<ModuleFactory> toBeRemoved = new ArrayList<>();
130 for (ModuleFactory moduleFactory : factoriesHolder.getModuleFactories()) {
131 if (oldSet.contains(moduleFactory) == false) {
132 toBeAdded.add(moduleFactory);
135 for (ModuleFactory moduleFactory : lastListOfFactories) {
136 if (newSet.contains(moduleFactory) == false) {
137 toBeRemoved.add(moduleFactory);
140 // add default modules
141 for (ModuleFactory moduleFactory : toBeAdded) {
142 BundleContext bundleContext = getModuleFactoryBundleContext(moduleFactory.getImplementationName());
143 Set<? extends Module> defaultModules = moduleFactory.getDefaultModules(dependencyResolverManager,
145 for (Module module : defaultModules) {
146 // ensure default module to be registered to jmx even if its module factory does not use dependencyResolverFactory
147 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(module.getIdentifier());
148 final ObjectName objectName;
150 boolean defaultBean = true;
151 objectName = putConfigBeanToJMXAndInternalMaps(module.getIdentifier(), module, moduleFactory, null,
152 dependencyResolver, defaultBean, bundleContext);
153 } catch (InstanceAlreadyExistsException e) {
154 throw new IllegalStateException(e);
157 // register default module as every possible service
158 final Set<ServiceInterfaceAnnotation> serviceInterfaceAnnotations = InterfacesHelper.getServiceInterfaceAnnotations(moduleFactory);
159 for (String qname : InterfacesHelper.getQNames(serviceInterfaceAnnotations)) {
161 saveServiceReference(qname, module.getIdentifier().getInstanceName(), objectName);
162 } catch (InstanceNotFoundException e) {
163 throw new IllegalStateException("Unable to register default module instance " + module + " as a service of " + qname, e);
169 // remove modules belonging to removed factories
170 for (ModuleFactory removedFactory : toBeRemoved) {
171 List<ModuleIdentifier> modulesOfRemovedFactory = dependencyResolverManager.findAllByFactory(removedFactory);
172 for (ModuleIdentifier name : modulesOfRemovedFactory) {
173 // remove service refs
174 final ModuleFactory moduleFactory = dependencyResolverManager.findModuleInternalTransactionalInfo(name).getModuleFactory();
175 final Set<ServiceInterfaceAnnotation> serviceInterfaceAnnotations = InterfacesHelper.getServiceInterfaceAnnotations(moduleFactory);
176 for (String qname : InterfacesHelper.getQNames(serviceInterfaceAnnotations)) {
178 removeServiceReference(qname, name.getInstanceName());
179 } catch (InstanceNotFoundException e) {
180 throw new IllegalStateException("Unable to UNregister default module instance " + name + " as a service of " + qname, e);
191 private synchronized void copyExistingModule(ModuleInternalInfo oldConfigBeanInfo) throws InstanceAlreadyExistsException {
193 transactionStatus.checkNotCommitStarted();
194 transactionStatus.checkNotAborted();
195 ModuleIdentifier moduleIdentifier = oldConfigBeanInfo.getIdentifier();
196 dependencyResolverManager.assertNotExists(moduleIdentifier);
198 ModuleFactory moduleFactory;
201 moduleFactory = factoriesHolder.findByModuleName(moduleIdentifier.getFactoryName());
202 bc = getModuleFactoryBundleContext(moduleFactory.getImplementationName());
203 } catch (InstanceNotFoundException e) {
204 throw new IllegalStateException(e);
208 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(moduleIdentifier);
211 module = moduleFactory.createModule(
212 moduleIdentifier.getInstanceName(), dependencyResolver,
213 oldConfigBeanInfo.getReadableModule(), bc);
214 } catch (Exception e) {
215 throw new IllegalStateException(format(
216 "Error while copying old configuration from %s to %s",
217 oldConfigBeanInfo, moduleFactory), e);
219 putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module, moduleFactory, oldConfigBeanInfo, dependencyResolver,
220 oldConfigBeanInfo.isDefaultBean(), bc);
224 public synchronized ObjectName createModule(String factoryName,
225 String instanceName) throws InstanceAlreadyExistsException {
227 transactionStatus.checkNotCommitStarted();
228 transactionStatus.checkNotAborted();
229 ModuleIdentifier moduleIdentifier = new ModuleIdentifier(factoryName, instanceName);
230 dependencyResolverManager.assertNotExists(moduleIdentifier);
233 ModuleFactory moduleFactory;
235 moduleFactory = factoriesHolder.findByModuleName(factoryName);
236 } catch (InstanceNotFoundException e) {
237 throw new IllegalArgumentException(e);
239 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(moduleIdentifier);
240 BundleContext bundleContext = getModuleFactoryBundleContext(moduleFactory.getImplementationName());
241 Module module = moduleFactory.createModule(instanceName, dependencyResolver,
243 boolean defaultBean = false;
244 return putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module,
245 moduleFactory, null, dependencyResolver, defaultBean, bundleContext);
248 private synchronized ObjectName putConfigBeanToJMXAndInternalMaps(
249 ModuleIdentifier moduleIdentifier, Module module,
250 ModuleFactory moduleFactory,
251 @Nullable ModuleInternalInfo maybeOldConfigBeanInfo, DependencyResolver dependencyResolver,
252 boolean isDefaultBean, BundleContext bundleContext)
253 throws InstanceAlreadyExistsException {
255 LOG.debug("Adding module {} to transaction {}", moduleIdentifier, this);
256 if (moduleIdentifier.equals(module.getIdentifier()) == false) {
257 throw new IllegalStateException("Incorrect name reported by module. Expected "
258 + moduleIdentifier + ", got " + module.getIdentifier());
260 if (dependencyResolver.getIdentifier().equals(moduleIdentifier) == false) {
261 throw new IllegalStateException("Incorrect name reported by dependency resolver. Expected "
262 + moduleIdentifier + ", got " + dependencyResolver.getIdentifier());
264 DynamicMBean writableDynamicWrapper = new DynamicWritableWrapper(
265 module, moduleIdentifier, getTransactionIdentifier(),
266 readOnlyAtomicBoolean, transactionsMBeanServer,
269 ObjectName writableON = ObjectNameUtil.createTransactionModuleON(
270 getTransactionIdentifier().getName(), moduleIdentifier);
271 // put wrapper to jmx
272 TransactionModuleJMXRegistration transactionModuleJMXRegistration = getTxModuleJMXRegistrator()
273 .registerMBean(writableDynamicWrapper, writableON);
275 dependencyResolverManager.put(
276 moduleIdentifier, module, moduleFactory,
277 maybeOldConfigBeanInfo, transactionModuleJMXRegistration, isDefaultBean, bundleContext);
282 public synchronized void destroyModule(ObjectName objectName) throws InstanceNotFoundException {
283 checkTransactionName(objectName);
284 ObjectNameUtil.checkDomain(objectName);
285 ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(objectName,
286 ObjectNameUtil.TYPE_MODULE);
287 destroyModule(moduleIdentifier);
290 private void checkTransactionName(ObjectName objectName) {
291 String foundTransactionName = ObjectNameUtil
292 .getTransactionName(objectName);
293 if (getTransactionIdentifier().getName().equals(foundTransactionName) == false) {
294 throw new IllegalArgumentException("Wrong transaction name "
299 private synchronized void destroyModule(ModuleIdentifier moduleIdentifier) {
300 LOG.debug("Destroying module {} in transaction {}", moduleIdentifier, this);
301 transactionStatus.checkNotAborted();
303 ModuleInternalTransactionalInfo found = dependencyResolverManager.findModuleInternalTransactionalInfo(moduleIdentifier);
304 if (blankTransaction == false &&
305 found.isDefaultBean()) {
306 LOG.warn("Warning: removing default bean. This will be forbidden in next version of config-subsystem");
308 // first remove refNames, it checks for objectname existence
311 writableSRRegistry.removeServiceReferences(
312 ObjectNameUtil.createTransactionModuleON(getTransactionName(), moduleIdentifier));
313 } catch (InstanceNotFoundException e) {
314 LOG.error("Possible code error: cannot find {} in {}", moduleIdentifier, writableSRRegistry);
315 throw new IllegalStateException("Possible code error: cannot find " + moduleIdentifier, e);
318 ModuleInternalTransactionalInfo removedTInfo = dependencyResolverManager.destroyModule(moduleIdentifier);
320 removedTInfo.getTransactionModuleJMXRegistration().close();
324 public long getParentVersion() {
325 return parentVersion;
329 public long getVersion() {
330 return currentVersion;
334 public synchronized void validateConfig() throws ValidationException {
335 if (configBeanModificationDisabled.get()) {
336 throw new IllegalStateException("Cannot start validation");
338 configBeanModificationDisabled.set(true);
342 configBeanModificationDisabled.set(false);
346 private void validateNoLocks() throws ValidationException {
347 transactionStatus.checkNotAborted();
348 LOG.trace("Validating transaction {}", getTransactionIdentifier());
350 List<ValidationException> collectedExceptions = new ArrayList<>();
351 for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
352 .getAllModules().entrySet()) {
353 ModuleIdentifier name = entry.getKey();
354 Module module = entry.getValue();
357 } catch (Exception e) {
358 LOG.warn("Validation exception in {}", getTransactionName(),
360 collectedExceptions.add(ValidationException
361 .createForSingleException(name, e));
364 if (!collectedExceptions.isEmpty()) {
365 throw ValidationException
366 .createFromCollectedValidationExceptions(collectedExceptions);
368 LOG.trace("Validated transaction {}", getTransactionIdentifier());
372 * If this method passes validation, it will grab
373 * {@link TransactionStatus#secondPhaseCommitStarted} lock. This lock will
374 * prevent calling @{link #validateBeforeCommitAndLockTransaction},
375 * effectively only allowing to call {@link #secondPhaseCommit} after
376 * successful return of this method.
379 public synchronized CommitInfo validateBeforeCommitAndLockTransaction()
380 throws ValidationException {
381 transactionStatus.checkNotAborted();
382 transactionStatus.checkNotCommitStarted();
383 configBeanModificationDisabled.set(true);
386 } catch (ValidationException e) {
387 LOG.trace("Commit failed on validation");
388 configBeanModificationDisabled.set(false); // recoverable error
391 // errors in this state are not recoverable. modules are not mutable
393 transactionStatus.setSecondPhaseCommitStarted();
394 return dependencyResolverManager.toCommitInfo();
401 public synchronized List<ModuleIdentifier> secondPhaseCommit() {
402 transactionStatus.checkNotAborted();
403 transactionStatus.checkCommitStarted();
404 if (configBeanModificationDisabled.get() == false) {
405 throw new IllegalStateException(
406 "Internal error - validateBeforeCommitAndLockTransaction should be called "
407 + "to obtain a lock");
410 LOG.trace("Committing transaction {}", getTransactionIdentifier());
412 Map<ModuleIdentifier, Module> allModules = dependencyResolverManager.getAllModules();
414 // call getInstance() on all Modules from top to bottom (from source to target of the dependency relation)
415 // The source of a dependency closes itself and calls getInstance recursively on the dependencies (in case of reconfiguration)
416 // This makes close() calls from top to bottom while createInstance() calls are performed bottom to top
417 List<ModuleIdentifier> sortedModuleIdentifiers = Lists.reverse(dependencyResolverManager.getSortedModuleIdentifiers());
418 for (ModuleIdentifier moduleIdentifier : sortedModuleIdentifiers) {
419 Module module = allModules.get(moduleIdentifier);
422 LOG.debug("About to commit {} in transaction {}",
423 moduleIdentifier, getTransactionIdentifier());
424 AutoCloseable instance = module.getInstance();
425 checkNotNull(instance, "Instance is null:{} in transaction {}", moduleIdentifier, getTransactionIdentifier());
426 } catch (Exception e) {
427 LOG.error("Commit failed on {} in transaction {}", moduleIdentifier,
428 getTransactionIdentifier(), e);
430 throw new IllegalStateException(
431 format("Error - getInstance() failed for %s in transaction %s",
432 moduleIdentifier, getTransactionIdentifier()), e);
436 LOG.trace("Committed configuration {}", getTransactionIdentifier());
437 transactionStatus.setCommitted();
439 return sortedModuleIdentifiers;
443 public synchronized void abortConfig() {
444 transactionStatus.checkNotCommitStarted();
445 transactionStatus.checkNotAborted();
449 private void internalAbort() {
450 LOG.trace("Aborting {}", this);
451 transactionStatus.setAborted();
455 public void close() {
456 dependencyResolverManager.close();
457 txLookupRegistry.close();
461 public ObjectName getControllerObjectName() {
466 public String getTransactionName() {
467 return getTransactionIdentifier().getName();
474 public Set<ObjectName> lookupConfigBeans() {
475 return txLookupRegistry.lookupConfigBeans();
482 public Set<ObjectName> lookupConfigBeans(String moduleName) {
483 return txLookupRegistry.lookupConfigBeans(moduleName);
490 public ObjectName lookupConfigBean(String moduleName, String instanceName)
491 throws InstanceNotFoundException {
492 return txLookupRegistry.lookupConfigBean(moduleName, instanceName);
499 public Set<ObjectName> lookupConfigBeans(String moduleName, String instanceName) {
500 return txLookupRegistry.lookupConfigBeans(moduleName, instanceName);
507 public void checkConfigBeanExists(ObjectName objectName) throws InstanceNotFoundException {
508 txLookupRegistry.checkConfigBeanExists(objectName);
516 public Set<ObjectName> lookupRuntimeBeans() {
517 return txLookupRegistry.lookupRuntimeBeans();
524 public Set<ObjectName> lookupRuntimeBeans(String moduleName,
525 String instanceName) {
526 return txLookupRegistry.lookupRuntimeBeans(moduleName, instanceName);
535 public Set<String> getAvailableModuleNames() {
536 return factoriesHolder.getModuleNames();
540 public boolean isClosed() {
541 return transactionStatus.isAbortedOrCommitted();
545 public String toString() {
546 StringBuilder sb = new StringBuilder();
547 sb.append("transactionName=");
548 sb.append(getTransactionName());
549 return sb.toString();
552 // @VisibleForTesting
554 TransactionModuleJMXRegistrator getTxModuleJMXRegistrator() {
555 return txLookupRegistry.getTxModuleJMXRegistrator();
558 public TransactionIdentifier getName() {
559 return getTransactionIdentifier();
563 public List<ModuleFactory> getCurrentlyRegisteredFactories() {
564 return new ArrayList<>(factoriesHolder.getModuleFactories());
568 public TransactionIdentifier getIdentifier() {
569 return getTransactionIdentifier();
573 public BundleContext getModuleFactoryBundleContext(String factoryName) {
574 Map.Entry<ModuleFactory, BundleContext> factoryBundleContextEntry = this.currentlyRegisteredFactories.get(factoryName);
575 if (factoryBundleContextEntry == null || factoryBundleContextEntry.getValue() == null) {
576 throw new NullPointerException("Bundle context of " + factoryName + " ModuleFactory not found.");
578 return factoryBundleContextEntry.getValue();
581 // service reference functionality:
585 public synchronized ObjectName lookupConfigBeanByServiceInterfaceName(String serviceInterfaceQName, String refName) {
586 return writableSRRegistry.lookupConfigBeanByServiceInterfaceName(serviceInterfaceQName, refName);
590 public synchronized Map<String, Map<String, ObjectName>> getServiceMapping() {
591 return writableSRRegistry.getServiceMapping();
595 public synchronized Map<String, ObjectName> lookupServiceReferencesByServiceInterfaceName(String serviceInterfaceQName) {
596 return writableSRRegistry.lookupServiceReferencesByServiceInterfaceName(serviceInterfaceQName);
600 public synchronized Set<String> lookupServiceInterfaceNames(ObjectName objectName) throws InstanceNotFoundException {
601 return writableSRRegistry.lookupServiceInterfaceNames(objectName);
605 public synchronized String getServiceInterfaceName(String namespace, String localName) {
606 return writableSRRegistry.getServiceInterfaceName(namespace, localName);
610 public synchronized ObjectName saveServiceReference(String serviceInterfaceName, String refName, ObjectName moduleON) throws InstanceNotFoundException {
611 return writableSRRegistry.saveServiceReference(serviceInterfaceName, refName, moduleON);
615 public synchronized void removeServiceReference(String serviceInterfaceName, String refName) throws InstanceNotFoundException {
616 writableSRRegistry.removeServiceReference(serviceInterfaceName, refName);
620 public synchronized void removeAllServiceReferences() {
621 writableSRRegistry.removeAllServiceReferences();
625 public boolean removeServiceReferences(ObjectName objectName) throws InstanceNotFoundException {
626 return writableSRRegistry.removeServiceReferences(objectName);
630 public SearchableServiceReferenceWritableRegistry getWritableRegistry() {
631 return writableSRRegistry;
635 public TransactionIdentifier getTransactionIdentifier() {
636 return txLookupRegistry.getTransactionIdentifier();
640 public Set<String> getAvailableModuleFactoryQNames() {
641 return txLookupRegistry.getAvailableModuleFactoryQNames();
645 public void checkServiceReferenceExists(ObjectName objectName) throws InstanceNotFoundException {
646 writableSRRegistry.checkServiceReferenceExists(objectName);
650 public ObjectName getServiceReference(String serviceInterfaceQName, String refName) throws InstanceNotFoundException {
651 return writableSRRegistry.getServiceReference(serviceInterfaceQName, refName);