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;
12 import com.google.common.collect.Lists;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.HashSet;
16 import java.util.List;
18 import java.util.Map.Entry;
20 import java.util.concurrent.atomic.AtomicBoolean;
21 import javax.annotation.Nullable;
22 import javax.annotation.concurrent.GuardedBy;
23 import javax.management.DynamicMBean;
24 import javax.management.InstanceAlreadyExistsException;
25 import javax.management.InstanceNotFoundException;
26 import javax.management.MBeanServer;
27 import javax.management.ObjectName;
28 import org.opendaylight.controller.config.api.DependencyResolver;
29 import org.opendaylight.controller.config.api.ModuleFactoryNotFoundException;
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)
192 throws InstanceAlreadyExistsException {
194 transactionStatus.checkNotCommitStarted();
195 transactionStatus.checkNotAborted();
196 ModuleIdentifier moduleIdentifier = oldConfigBeanInfo.getIdentifier();
197 dependencyResolverManager.assertNotExists(moduleIdentifier);
199 ModuleFactory moduleFactory;
202 moduleFactory = factoriesHolder.findByModuleName(moduleIdentifier.getFactoryName());
203 bc = getModuleFactoryBundleContext(moduleFactory.getImplementationName());
204 } catch (ModuleFactoryNotFoundException e) {
205 throw new IllegalStateException(e);
209 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(moduleIdentifier);
212 module = moduleFactory.createModule(
213 moduleIdentifier.getInstanceName(), dependencyResolver,
214 oldConfigBeanInfo.getReadableModule(), bc);
215 } catch (Exception e) {
216 throw new IllegalStateException(format(
217 "Error while copying old configuration from %s to %s",
218 oldConfigBeanInfo, moduleFactory), e);
220 putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module, moduleFactory, oldConfigBeanInfo, dependencyResolver,
221 oldConfigBeanInfo.isDefaultBean(), bc);
225 public synchronized ObjectName createModule(String factoryName, String instanceName)
226 throws InstanceAlreadyExistsException {
228 transactionStatus.checkNotCommitStarted();
229 transactionStatus.checkNotAborted();
230 ModuleIdentifier moduleIdentifier = new ModuleIdentifier(factoryName, instanceName);
231 dependencyResolverManager.assertNotExists(moduleIdentifier);
234 ModuleFactory moduleFactory = factoriesHolder.findByModuleName(factoryName);
236 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(moduleIdentifier);
237 BundleContext bundleContext = getModuleFactoryBundleContext(moduleFactory.getImplementationName());
238 Module module = moduleFactory.createModule(instanceName, dependencyResolver,
240 boolean defaultBean = false;
241 return putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module,
242 moduleFactory, null, dependencyResolver, defaultBean, bundleContext);
245 private synchronized ObjectName putConfigBeanToJMXAndInternalMaps(
246 ModuleIdentifier moduleIdentifier, Module module,
247 ModuleFactory moduleFactory,
248 @Nullable ModuleInternalInfo maybeOldConfigBeanInfo, DependencyResolver dependencyResolver,
249 boolean isDefaultBean, BundleContext bundleContext)
250 throws InstanceAlreadyExistsException {
252 LOG.debug("Adding module {} to transaction {}", moduleIdentifier, this);
253 if (moduleIdentifier.equals(module.getIdentifier()) == false) {
254 throw new IllegalStateException("Incorrect name reported by module. Expected "
255 + moduleIdentifier + ", got " + module.getIdentifier());
257 if (dependencyResolver.getIdentifier().equals(moduleIdentifier) == false) {
258 throw new IllegalStateException("Incorrect name reported by dependency resolver. Expected "
259 + moduleIdentifier + ", got " + dependencyResolver.getIdentifier());
261 DynamicMBean writableDynamicWrapper = new DynamicWritableWrapper(
262 module, moduleIdentifier, getTransactionIdentifier(),
263 readOnlyAtomicBoolean, transactionsMBeanServer,
266 ObjectName writableON = ObjectNameUtil.createTransactionModuleON(
267 getTransactionIdentifier().getName(), moduleIdentifier);
268 // put wrapper to jmx
269 TransactionModuleJMXRegistration transactionModuleJMXRegistration = getTxModuleJMXRegistrator()
270 .registerMBean(writableDynamicWrapper, writableON);
272 dependencyResolverManager.put(
273 moduleIdentifier, module, moduleFactory,
274 maybeOldConfigBeanInfo, transactionModuleJMXRegistration, isDefaultBean, bundleContext);
279 public synchronized void destroyModule(ObjectName objectName) throws InstanceNotFoundException {
280 checkTransactionName(objectName);
281 ObjectNameUtil.checkDomain(objectName);
282 ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(objectName,
283 ObjectNameUtil.TYPE_MODULE);
284 destroyModule(moduleIdentifier);
287 private void checkTransactionName(ObjectName objectName) {
288 String foundTransactionName = ObjectNameUtil
289 .getTransactionName(objectName);
290 if (getTransactionIdentifier().getName().equals(foundTransactionName) == false) {
291 throw new IllegalArgumentException("Wrong transaction name "
296 private synchronized void destroyModule(ModuleIdentifier moduleIdentifier) {
297 LOG.debug("Destroying module {} in transaction {}", moduleIdentifier, this);
298 transactionStatus.checkNotAborted();
300 ModuleInternalTransactionalInfo found = dependencyResolverManager.findModuleInternalTransactionalInfo(moduleIdentifier);
301 if (blankTransaction == false &&
302 found.isDefaultBean()) {
303 LOG.warn("Warning: removing default bean. This will be forbidden in next version of config-subsystem");
305 // first remove refNames, it checks for objectname existence
308 writableSRRegistry.removeServiceReferences(
309 ObjectNameUtil.createTransactionModuleON(getTransactionName(), moduleIdentifier));
310 } catch (InstanceNotFoundException e) {
311 LOG.error("Possible code error: cannot find {} in {}", moduleIdentifier, writableSRRegistry);
312 throw new IllegalStateException("Possible code error: cannot find " + moduleIdentifier, e);
315 ModuleInternalTransactionalInfo removedTInfo = dependencyResolverManager.destroyModule(moduleIdentifier);
317 removedTInfo.getTransactionModuleJMXRegistration().close();
321 public long getParentVersion() {
322 return parentVersion;
326 public long getVersion() {
327 return currentVersion;
331 public synchronized void validateConfig() throws ValidationException {
332 if (configBeanModificationDisabled.get()) {
333 throw new IllegalStateException("Cannot start validation");
335 configBeanModificationDisabled.set(true);
339 configBeanModificationDisabled.set(false);
343 private void validateNoLocks() throws ValidationException {
344 transactionStatus.checkNotAborted();
345 LOG.trace("Validating transaction {}", getTransactionIdentifier());
347 List<ValidationException> collectedExceptions = new ArrayList<>();
348 for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
349 .getAllModules().entrySet()) {
350 ModuleIdentifier name = entry.getKey();
351 Module module = entry.getValue();
354 } catch (Exception e) {
355 LOG.warn("Validation exception in {}", getTransactionName(),
357 collectedExceptions.add(ValidationException
358 .createForSingleException(name, e));
361 if (!collectedExceptions.isEmpty()) {
362 throw ValidationException
363 .createFromCollectedValidationExceptions(collectedExceptions);
365 LOG.trace("Validated transaction {}", getTransactionIdentifier());
369 * If this method passes validation, it will grab
370 * {@link TransactionStatus#secondPhaseCommitStarted} lock. This lock will
371 * prevent calling @{link #validateBeforeCommitAndLockTransaction},
372 * effectively only allowing to call {@link #secondPhaseCommit} after
373 * successful return of this method.
376 public synchronized CommitInfo validateBeforeCommitAndLockTransaction()
377 throws ValidationException {
378 transactionStatus.checkNotAborted();
379 transactionStatus.checkNotCommitStarted();
380 configBeanModificationDisabled.set(true);
383 } catch (ValidationException e) {
384 LOG.trace("Commit failed on validation");
385 configBeanModificationDisabled.set(false); // recoverable error
388 // errors in this state are not recoverable. modules are not mutable
390 transactionStatus.setSecondPhaseCommitStarted();
391 return dependencyResolverManager.toCommitInfo();
398 public synchronized List<ModuleIdentifier> secondPhaseCommit() {
399 transactionStatus.checkNotAborted();
400 transactionStatus.checkCommitStarted();
401 if (configBeanModificationDisabled.get() == false) {
402 throw new IllegalStateException(
403 "Internal error - validateBeforeCommitAndLockTransaction should be called "
404 + "to obtain a lock");
407 LOG.trace("Committing transaction {}", getTransactionIdentifier());
409 Map<ModuleIdentifier, Module> allModules = dependencyResolverManager.getAllModules();
411 // call getInstance() on all Modules from top to bottom (from source to target of the dependency relation)
412 // The source of a dependency closes itself and calls getInstance recursively on the dependencies (in case of reconfiguration)
413 // This makes close() calls from top to bottom while createInstance() calls are performed bottom to top
414 List<ModuleIdentifier> sortedModuleIdentifiers = Lists.reverse(dependencyResolverManager.getSortedModuleIdentifiers());
415 for (ModuleIdentifier moduleIdentifier : sortedModuleIdentifiers) {
416 Module module = allModules.get(moduleIdentifier);
419 LOG.debug("About to commit {} in transaction {}",
420 moduleIdentifier, getTransactionIdentifier());
421 AutoCloseable instance = module.getInstance();
422 checkNotNull(instance, "Instance is null:{} in transaction {}", moduleIdentifier, getTransactionIdentifier());
423 } catch (Exception e) {
424 LOG.error("Commit failed on {} in transaction {}", moduleIdentifier,
425 getTransactionIdentifier(), e);
427 throw new IllegalStateException(
428 format("Error - getInstance() failed for %s in transaction %s",
429 moduleIdentifier, getTransactionIdentifier()), e);
433 LOG.trace("Committed configuration {}", getTransactionIdentifier());
434 transactionStatus.setCommitted();
436 return sortedModuleIdentifiers;
440 public void abortConfig() {
441 transactionStatus.checkNotCommitStarted();
442 transactionStatus.checkNotAborted();
446 private void internalAbort() {
447 LOG.trace("Aborting {}", this);
448 transactionStatus.setAborted();
453 public void close() {
454 dependencyResolverManager.close();
455 txLookupRegistry.close();
459 public ObjectName getControllerObjectName() {
464 public String getTransactionName() {
465 return getTransactionIdentifier().getName();
472 public Set<ObjectName> lookupConfigBeans() {
473 return txLookupRegistry.lookupConfigBeans();
480 public Set<ObjectName> lookupConfigBeans(String moduleName) {
481 return txLookupRegistry.lookupConfigBeans(moduleName);
488 public ObjectName lookupConfigBean(String moduleName, String instanceName)
489 throws InstanceNotFoundException {
490 return txLookupRegistry.lookupConfigBean(moduleName, instanceName);
497 public Set<ObjectName> lookupConfigBeans(String moduleName, String instanceName) {
498 return txLookupRegistry.lookupConfigBeans(moduleName, instanceName);
505 public void checkConfigBeanExists(ObjectName objectName) throws InstanceNotFoundException {
506 txLookupRegistry.checkConfigBeanExists(objectName);
514 public Set<ObjectName> lookupRuntimeBeans() {
515 return txLookupRegistry.lookupRuntimeBeans();
522 public Set<ObjectName> lookupRuntimeBeans(String moduleName,
523 String instanceName) {
524 return txLookupRegistry.lookupRuntimeBeans(moduleName, instanceName);
533 public Set<String> getAvailableModuleNames() {
534 return factoriesHolder.getModuleNames();
538 public boolean isClosed() {
539 return transactionStatus.isAbortedOrCommitted();
543 public String toString() {
544 StringBuilder sb = new StringBuilder();
545 sb.append("transactionName=");
546 sb.append(getTransactionName());
547 return sb.toString();
550 // @VisibleForTesting
552 TransactionModuleJMXRegistrator getTxModuleJMXRegistrator() {
553 return txLookupRegistry.getTxModuleJMXRegistrator();
556 public TransactionIdentifier getName() {
557 return getTransactionIdentifier();
561 public List<ModuleFactory> getCurrentlyRegisteredFactories() {
562 return new ArrayList<>(factoriesHolder.getModuleFactories());
566 public TransactionIdentifier getIdentifier() {
567 return getTransactionIdentifier();
571 public BundleContext getModuleFactoryBundleContext(String factoryName) {
572 Map.Entry<ModuleFactory, BundleContext> factoryBundleContextEntry = this.currentlyRegisteredFactories.get(factoryName);
573 if (factoryBundleContextEntry == null || factoryBundleContextEntry.getValue() == null) {
574 throw new NullPointerException("Bundle context of " + factoryName + " ModuleFactory not found.");
576 return factoryBundleContextEntry.getValue();
579 // service reference functionality:
583 public synchronized ObjectName lookupConfigBeanByServiceInterfaceName(String serviceInterfaceQName, String refName) {
584 return writableSRRegistry.lookupConfigBeanByServiceInterfaceName(serviceInterfaceQName, refName);
588 public synchronized Map<String, Map<String, ObjectName>> getServiceMapping() {
589 return writableSRRegistry.getServiceMapping();
593 public synchronized Map<String, ObjectName> lookupServiceReferencesByServiceInterfaceName(String serviceInterfaceQName) {
594 return writableSRRegistry.lookupServiceReferencesByServiceInterfaceName(serviceInterfaceQName);
598 public synchronized Set<String> lookupServiceInterfaceNames(ObjectName objectName) throws InstanceNotFoundException {
599 return writableSRRegistry.lookupServiceInterfaceNames(objectName);
603 public synchronized String getServiceInterfaceName(String namespace, String localName) {
604 return writableSRRegistry.getServiceInterfaceName(namespace, localName);
608 public synchronized ObjectName saveServiceReference(String serviceInterfaceName, String refName, ObjectName moduleON) throws InstanceNotFoundException {
609 return writableSRRegistry.saveServiceReference(serviceInterfaceName, refName, moduleON);
613 public synchronized void removeServiceReference(String serviceInterfaceName, String refName) throws InstanceNotFoundException {
614 writableSRRegistry.removeServiceReference(serviceInterfaceName, refName);
618 public synchronized void removeAllServiceReferences() {
619 writableSRRegistry.removeAllServiceReferences();
623 public boolean removeServiceReferences(ObjectName objectName) throws InstanceNotFoundException {
624 return writableSRRegistry.removeServiceReferences(objectName);
628 public SearchableServiceReferenceWritableRegistry getWritableRegistry() {
629 return writableSRRegistry;
633 public TransactionIdentifier getTransactionIdentifier() {
634 return txLookupRegistry.getTransactionIdentifier();
638 public Set<String> getAvailableModuleFactoryQNames() {
639 return txLookupRegistry.getAvailableModuleFactoryQNames();
643 public void checkServiceReferenceExists(ObjectName objectName) throws InstanceNotFoundException {
644 writableSRRegistry.checkServiceReferenceExists(objectName);
648 public ObjectName getServiceReference(String serviceInterfaceQName, String refName) throws InstanceNotFoundException {
649 return writableSRRegistry.getServiceReference(serviceInterfaceQName, refName);