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 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.ModuleIdentifier;
30 import org.opendaylight.controller.config.api.ValidationException;
31 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
32 import org.opendaylight.controller.config.manager.impl.dependencyresolver.DependencyResolverManager;
33 import org.opendaylight.controller.config.manager.impl.dependencyresolver.ModuleInternalTransactionalInfo;
34 import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicWritableWrapper;
35 import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean;
36 import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean.ReadOnlyAtomicBooleanImpl;
37 import org.opendaylight.controller.config.manager.impl.factoriesresolver.HierarchicalConfigMBeanFactoriesHolder;
38 import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator;
39 import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator.TransactionModuleJMXRegistration;
40 import org.opendaylight.controller.config.spi.Module;
41 import org.opendaylight.controller.config.spi.ModuleFactory;
42 import org.opendaylight.yangtools.concepts.Identifiable;
43 import org.opendaylight.yangtools.yang.data.impl.codec.CodecRegistry;
44 import org.osgi.framework.BundleContext;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
48 * This is a JMX bean representing current transaction. It contains
49 * transaction identifier, unique version and parent version for
52 class ConfigTransactionControllerImpl implements
53 ConfigTransactionControllerInternal,
54 ConfigTransactionControllerImplMXBean,
55 Identifiable<TransactionIdentifier> {
56 private static final Logger LOG = LoggerFactory.getLogger(ConfigTransactionControllerImpl.class);
58 private final ConfigTransactionLookupRegistry txLookupRegistry;
59 private final ObjectName controllerON;
61 private final long parentVersion, currentVersion;
62 private final HierarchicalConfigMBeanFactoriesHolder factoriesHolder;
63 private final DependencyResolverManager dependencyResolverManager;
64 private final TransactionStatus transactionStatus;
65 private final MBeanServer transactionsMBeanServer;
66 private final Map<String, Map.Entry<ModuleFactory, BundleContext>> currentlyRegisteredFactories;
69 * Disables ability of {@link DynamicWritableWrapper} to change attributes
73 private final AtomicBoolean configBeanModificationDisabled = new AtomicBoolean(
75 private final ReadOnlyAtomicBoolean readOnlyAtomicBoolean = new ReadOnlyAtomicBooleanImpl(
76 configBeanModificationDisabled);
77 private final MBeanServer configMBeanServer;
79 private final boolean blankTransaction;
82 private final SearchableServiceReferenceWritableRegistry writableSRRegistry;
84 public ConfigTransactionControllerImpl(ConfigTransactionLookupRegistry txLookupRegistry,
85 long parentVersion, CodecRegistry codecRegistry, long currentVersion,
86 Map<String, Entry<ModuleFactory, BundleContext>> currentlyRegisteredFactories,
87 MBeanServer transactionsMBeanServer, MBeanServer configMBeanServer,
88 boolean blankTransaction, SearchableServiceReferenceWritableRegistry writableSRRegistry) {
89 this.txLookupRegistry = txLookupRegistry;
90 String transactionName = txLookupRegistry.getTransactionIdentifier().getName();
91 this.controllerON = ObjectNameUtil.createTransactionControllerON(transactionName);
92 this.parentVersion = parentVersion;
93 this.currentVersion = currentVersion;
94 this.currentlyRegisteredFactories = currentlyRegisteredFactories;
95 this.factoriesHolder = new HierarchicalConfigMBeanFactoriesHolder(currentlyRegisteredFactories);
96 this.transactionStatus = new TransactionStatus();
97 this.dependencyResolverManager = new DependencyResolverManager(txLookupRegistry.getTransactionIdentifier(),
98 transactionStatus, writableSRRegistry, codecRegistry, transactionsMBeanServer);
99 this.transactionsMBeanServer = transactionsMBeanServer;
100 this.configMBeanServer = configMBeanServer;
101 this.blankTransaction = blankTransaction;
102 this.writableSRRegistry = writableSRRegistry;
106 public void copyExistingModulesAndProcessFactoryDiff(Collection<ModuleInternalInfo> existingModules, List<ModuleFactory> lastListOfFactories) {
107 // copy old configuration to this server
108 for (ModuleInternalInfo oldConfigInfo : existingModules) {
110 copyExistingModule(oldConfigInfo);
111 } catch (InstanceAlreadyExistsException e) {
112 throw new IllegalStateException("Error while copying " + oldConfigInfo, e);
115 processDefaultBeans(lastListOfFactories);
118 private synchronized void processDefaultBeans(List<ModuleFactory> lastListOfFactories) {
119 transactionStatus.checkNotCommitStarted();
120 transactionStatus.checkNotAborted();
122 Set<ModuleFactory> oldSet = new HashSet<>(lastListOfFactories);
123 Set<ModuleFactory> newSet = new HashSet<>(factoriesHolder.getModuleFactories());
125 List<ModuleFactory> toBeAdded = new ArrayList<>();
126 List<ModuleFactory> toBeRemoved = new ArrayList<>();
127 for (ModuleFactory moduleFactory : factoriesHolder.getModuleFactories()) {
128 if (oldSet.contains(moduleFactory) == false) {
129 toBeAdded.add(moduleFactory);
132 for (ModuleFactory moduleFactory : lastListOfFactories) {
133 if (newSet.contains(moduleFactory) == false) {
134 toBeRemoved.add(moduleFactory);
137 // add default modules
138 for (ModuleFactory moduleFactory : toBeAdded) {
139 BundleContext bundleContext = getModuleFactoryBundleContext(moduleFactory.getImplementationName());
140 Set<? extends Module> defaultModules = moduleFactory.getDefaultModules(dependencyResolverManager,
142 for (Module module : defaultModules) {
143 // ensure default module to be registered to jmx even if its module factory does not use dependencyResolverFactory
144 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(module.getIdentifier());
146 boolean defaultBean = true;
147 putConfigBeanToJMXAndInternalMaps(module.getIdentifier(), module, moduleFactory, null,
148 dependencyResolver, defaultBean, bundleContext);
149 } catch (InstanceAlreadyExistsException e) {
150 throw new IllegalStateException(e);
155 // remove modules belonging to removed factories
156 for (ModuleFactory removedFactory : toBeRemoved) {
157 List<ModuleIdentifier> modulesOfRemovedFactory = dependencyResolverManager.findAllByFactory(removedFactory);
158 for (ModuleIdentifier name : modulesOfRemovedFactory) {
165 private synchronized void copyExistingModule(ModuleInternalInfo oldConfigBeanInfo) throws InstanceAlreadyExistsException {
167 transactionStatus.checkNotCommitStarted();
168 transactionStatus.checkNotAborted();
169 ModuleIdentifier moduleIdentifier = oldConfigBeanInfo.getIdentifier();
170 dependencyResolverManager.assertNotExists(moduleIdentifier);
172 ModuleFactory moduleFactory;
175 moduleFactory = factoriesHolder.findByModuleName(moduleIdentifier.getFactoryName());
176 bc = getModuleFactoryBundleContext(moduleFactory.getImplementationName());
177 } catch (InstanceNotFoundException e) {
178 throw new IllegalStateException(e);
182 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(moduleIdentifier);
185 module = moduleFactory.createModule(
186 moduleIdentifier.getInstanceName(), dependencyResolver,
187 oldConfigBeanInfo.getReadableModule(), bc);
188 } catch (Exception e) {
189 throw new IllegalStateException(format(
190 "Error while copying old configuration from %s to %s",
191 oldConfigBeanInfo, moduleFactory), e);
193 putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module, moduleFactory, oldConfigBeanInfo, dependencyResolver,
194 oldConfigBeanInfo.isDefaultBean(), bc);
198 public synchronized ObjectName createModule(String factoryName,
199 String instanceName) throws InstanceAlreadyExistsException {
201 transactionStatus.checkNotCommitStarted();
202 transactionStatus.checkNotAborted();
203 ModuleIdentifier moduleIdentifier = new ModuleIdentifier(factoryName, instanceName);
204 dependencyResolverManager.assertNotExists(moduleIdentifier);
207 ModuleFactory moduleFactory;
209 moduleFactory = factoriesHolder.findByModuleName(factoryName);
210 } catch (InstanceNotFoundException e) {
211 throw new IllegalArgumentException(e);
213 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(moduleIdentifier);
214 BundleContext bundleContext = getModuleFactoryBundleContext(moduleFactory.getImplementationName());
215 Module module = moduleFactory.createModule(instanceName, dependencyResolver,
217 boolean defaultBean = false;
218 return putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module,
219 moduleFactory, null, dependencyResolver, defaultBean, bundleContext);
222 private synchronized ObjectName putConfigBeanToJMXAndInternalMaps(
223 ModuleIdentifier moduleIdentifier, Module module,
224 ModuleFactory moduleFactory,
225 @Nullable ModuleInternalInfo maybeOldConfigBeanInfo, DependencyResolver dependencyResolver,
226 boolean isDefaultBean, BundleContext bundleContext)
227 throws InstanceAlreadyExistsException {
229 LOG.debug("Adding module {} to transaction {}", moduleIdentifier, this);
230 if (moduleIdentifier.equals(module.getIdentifier()) == false) {
231 throw new IllegalStateException("Incorrect name reported by module. Expected "
232 + moduleIdentifier + ", got " + module.getIdentifier());
234 if (dependencyResolver.getIdentifier().equals(moduleIdentifier) == false) {
235 throw new IllegalStateException("Incorrect name reported by dependency resolver. Expected "
236 + moduleIdentifier + ", got " + dependencyResolver.getIdentifier());
238 DynamicMBean writableDynamicWrapper = new DynamicWritableWrapper(
239 module, moduleIdentifier, getTransactionIdentifier(),
240 readOnlyAtomicBoolean, transactionsMBeanServer,
243 ObjectName writableON = ObjectNameUtil.createTransactionModuleON(
244 getTransactionIdentifier().getName(), moduleIdentifier);
245 // put wrapper to jmx
246 TransactionModuleJMXRegistration transactionModuleJMXRegistration = getTxModuleJMXRegistrator()
247 .registerMBean(writableDynamicWrapper, writableON);
249 dependencyResolverManager.put(
250 moduleIdentifier, module, moduleFactory,
251 maybeOldConfigBeanInfo, transactionModuleJMXRegistration, isDefaultBean, bundleContext);
256 public synchronized void destroyModule(ObjectName objectName) throws InstanceNotFoundException {
257 checkTransactionName(objectName);
258 ObjectNameUtil.checkDomain(objectName);
259 ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(objectName,
260 ObjectNameUtil.TYPE_MODULE);
261 destroyModule(moduleIdentifier);
264 private void checkTransactionName(ObjectName objectName) {
265 String foundTransactionName = ObjectNameUtil
266 .getTransactionName(objectName);
267 if (getTransactionIdentifier().getName().equals(foundTransactionName) == false) {
268 throw new IllegalArgumentException("Wrong transaction name "
273 private synchronized void destroyModule(ModuleIdentifier moduleIdentifier) {
274 LOG.debug("Destroying module {} in transaction {}", moduleIdentifier, this);
275 transactionStatus.checkNotAborted();
277 ModuleInternalTransactionalInfo found = dependencyResolverManager.findModuleInternalTransactionalInfo(moduleIdentifier);
278 if (blankTransaction == false &&
279 found.isDefaultBean()) {
280 LOG.warn("Warning: removing default bean. This will be forbidden in next version of config-subsystem");
282 // first remove refNames, it checks for objectname existence
285 writableSRRegistry.removeServiceReferences(
286 ObjectNameUtil.createTransactionModuleON(getTransactionName(), moduleIdentifier));
287 } catch (InstanceNotFoundException e) {
288 LOG.error("Possible code error: cannot find {} in {}", moduleIdentifier, writableSRRegistry);
289 throw new IllegalStateException("Possible code error: cannot find " + moduleIdentifier, e);
292 ModuleInternalTransactionalInfo removedTInfo = dependencyResolverManager.destroyModule(moduleIdentifier);
294 removedTInfo.getTransactionModuleJMXRegistration().close();
298 public long getParentVersion() {
299 return parentVersion;
303 public long getVersion() {
304 return currentVersion;
308 public synchronized void validateConfig() throws ValidationException {
309 if (configBeanModificationDisabled.get()) {
310 throw new IllegalStateException("Cannot start validation");
312 configBeanModificationDisabled.set(true);
316 configBeanModificationDisabled.set(false);
320 private void validateNoLocks() throws ValidationException {
321 transactionStatus.checkNotAborted();
322 LOG.trace("Validating transaction {}", getTransactionIdentifier());
324 List<ValidationException> collectedExceptions = new ArrayList<>();
325 for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
326 .getAllModules().entrySet()) {
327 ModuleIdentifier name = entry.getKey();
328 Module module = entry.getValue();
331 } catch (Exception e) {
332 LOG.warn("Validation exception in {}", getTransactionName(),
334 collectedExceptions.add(ValidationException
335 .createForSingleException(name, e));
338 if (!collectedExceptions.isEmpty()) {
339 throw ValidationException
340 .createFromCollectedValidationExceptions(collectedExceptions);
342 LOG.trace("Validated transaction {}", getTransactionIdentifier());
346 * If this method passes validation, it will grab
347 * {@link TransactionStatus#secondPhaseCommitStarted} lock. This lock will
348 * prevent calling @{link #validateBeforeCommitAndLockTransaction},
349 * effectively only allowing to call {@link #secondPhaseCommit} after
350 * successful return of this method.
353 public synchronized CommitInfo validateBeforeCommitAndLockTransaction()
354 throws ValidationException {
355 transactionStatus.checkNotAborted();
356 transactionStatus.checkNotCommitStarted();
357 configBeanModificationDisabled.set(true);
360 } catch (ValidationException e) {
361 LOG.trace("Commit failed on validation");
362 configBeanModificationDisabled.set(false); // recoverable error
365 // errors in this state are not recoverable. modules are not mutable
367 transactionStatus.setSecondPhaseCommitStarted();
368 return dependencyResolverManager.toCommitInfo();
375 public synchronized List<ModuleIdentifier> secondPhaseCommit() {
376 transactionStatus.checkNotAborted();
377 transactionStatus.checkCommitStarted();
378 if (configBeanModificationDisabled.get() == false) {
379 throw new IllegalStateException(
380 "Internal error - validateBeforeCommitAndLockTransaction should be called "
381 + "to obtain a lock");
384 LOG.trace("Committing transaction {}", getTransactionIdentifier());
386 // call getInstance()
387 for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
388 .getAllModules().entrySet()) {
389 Module module = entry.getValue();
390 ModuleIdentifier name = entry.getKey();
392 LOG.debug("About to commit {} in transaction {}",
393 name, getTransactionIdentifier());
394 AutoCloseable instance = module.getInstance();
395 checkNotNull(instance, "Instance is null:{} in transaction {}", name, getTransactionIdentifier());
396 } catch (Exception e) {
397 LOG.error("Commit failed on {} in transaction {}", name,
398 getTransactionIdentifier(), e);
400 throw new IllegalStateException(
401 format("Error - getInstance() failed for %s in transaction %s",
402 name, getTransactionIdentifier()), e);
406 // count dependency order
408 LOG.trace("Committed configuration {}", getTransactionIdentifier());
409 transactionStatus.setCommitted();
411 return dependencyResolverManager.getSortedModuleIdentifiers();
415 public synchronized void abortConfig() {
416 transactionStatus.checkNotCommitStarted();
417 transactionStatus.checkNotAborted();
421 private void internalAbort() {
422 LOG.trace("Aborting {}", this);
423 transactionStatus.setAborted();
427 public void close() {
428 dependencyResolverManager.close();
429 txLookupRegistry.close();
433 public ObjectName getControllerObjectName() {
438 public String getTransactionName() {
439 return getTransactionIdentifier().getName();
446 public Set<ObjectName> lookupConfigBeans() {
447 return txLookupRegistry.lookupConfigBeans();
454 public Set<ObjectName> lookupConfigBeans(String moduleName) {
455 return txLookupRegistry.lookupConfigBeans(moduleName);
462 public ObjectName lookupConfigBean(String moduleName, String instanceName)
463 throws InstanceNotFoundException {
464 return txLookupRegistry.lookupConfigBean(moduleName, instanceName);
471 public Set<ObjectName> lookupConfigBeans(String moduleName, String instanceName) {
472 return txLookupRegistry.lookupConfigBeans(moduleName, instanceName);
479 public void checkConfigBeanExists(ObjectName objectName) throws InstanceNotFoundException {
480 txLookupRegistry.checkConfigBeanExists(objectName);
488 public Set<String> getAvailableModuleNames() {
489 return factoriesHolder.getModuleNames();
493 public boolean isClosed() {
494 return transactionStatus.isAbortedOrCommitted();
498 public String toString() {
499 StringBuilder sb = new StringBuilder();
500 sb.append("transactionName=");
501 sb.append(getTransactionName());
502 return sb.toString();
505 // @VisibleForTesting
507 TransactionModuleJMXRegistrator getTxModuleJMXRegistrator() {
508 return txLookupRegistry.getTxModuleJMXRegistrator();
511 public TransactionIdentifier getName() {
512 return getTransactionIdentifier();
516 public List<ModuleFactory> getCurrentlyRegisteredFactories() {
517 return new ArrayList<>(factoriesHolder.getModuleFactories());
521 public TransactionIdentifier getIdentifier() {
522 return getTransactionIdentifier();
526 public BundleContext getModuleFactoryBundleContext(String factoryName) {
527 Map.Entry<ModuleFactory, BundleContext> factoryBundleContextEntry = this.currentlyRegisteredFactories.get(factoryName);
528 if (factoryBundleContextEntry == null || factoryBundleContextEntry.getValue() == null) {
529 throw new NullPointerException("Bundle context of " + factoryName + " ModuleFactory not found.");
531 return factoryBundleContextEntry.getValue();
534 // service reference functionality:
538 public synchronized ObjectName lookupConfigBeanByServiceInterfaceName(String serviceInterfaceQName, String refName) {
539 return writableSRRegistry.lookupConfigBeanByServiceInterfaceName(serviceInterfaceQName, refName);
543 public synchronized Map<String, Map<String, ObjectName>> getServiceMapping() {
544 return writableSRRegistry.getServiceMapping();
548 public synchronized Map<String, ObjectName> lookupServiceReferencesByServiceInterfaceName(String serviceInterfaceQName) {
549 return writableSRRegistry.lookupServiceReferencesByServiceInterfaceName(serviceInterfaceQName);
553 public synchronized Set<String> lookupServiceInterfaceNames(ObjectName objectName) throws InstanceNotFoundException {
554 return writableSRRegistry.lookupServiceInterfaceNames(objectName);
558 public synchronized String getServiceInterfaceName(String namespace, String localName) {
559 return writableSRRegistry.getServiceInterfaceName(namespace, localName);
563 public synchronized ObjectName saveServiceReference(String serviceInterfaceName, String refName, ObjectName moduleON) throws InstanceNotFoundException {
564 return writableSRRegistry.saveServiceReference(serviceInterfaceName, refName, moduleON);
568 public synchronized void removeServiceReference(String serviceInterfaceName, String refName) throws InstanceNotFoundException {
569 writableSRRegistry.removeServiceReference(serviceInterfaceName, refName);
573 public synchronized void removeAllServiceReferences() {
574 writableSRRegistry.removeAllServiceReferences();
578 public boolean removeServiceReferences(ObjectName objectName) throws InstanceNotFoundException {
579 return writableSRRegistry.removeServiceReferences(objectName);
583 public SearchableServiceReferenceWritableRegistry getWritableRegistry() {
584 return writableSRRegistry;
588 public TransactionIdentifier getTransactionIdentifier() {
589 return txLookupRegistry.getTransactionIdentifier();
593 public Set<String> getAvailableModuleFactoryQNames() {
594 return txLookupRegistry.getAvailableModuleFactoryQNames();
598 public void checkServiceReferenceExists(ObjectName objectName) throws InstanceNotFoundException {
599 writableSRRegistry.checkServiceReferenceExists(objectName);
603 public ObjectName getServiceReference(String serviceInterfaceQName, String refName) throws InstanceNotFoundException {
604 return writableSRRegistry.getServiceReference(serviceInterfaceQName, refName);