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 org.opendaylight.controller.config.api.DependencyResolver;
11 import org.opendaylight.controller.config.api.ModuleIdentifier;
12 import org.opendaylight.controller.config.api.ServiceReferenceWritableRegistry;
13 import org.opendaylight.controller.config.api.ValidationException;
14 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
15 import org.opendaylight.controller.config.manager.impl.dependencyresolver.DependencyResolverManager;
16 import org.opendaylight.controller.config.manager.impl.dependencyresolver.ModuleInternalTransactionalInfo;
17 import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicWritableWrapper;
18 import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean;
19 import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean.ReadOnlyAtomicBooleanImpl;
20 import org.opendaylight.controller.config.manager.impl.factoriesresolver.HierarchicalConfigMBeanFactoriesHolder;
21 import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator;
22 import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator.TransactionModuleJMXRegistration;
23 import org.opendaylight.controller.config.spi.Module;
24 import org.opendaylight.controller.config.spi.ModuleFactory;
25 import org.opendaylight.yangtools.concepts.Identifiable;
26 import org.opendaylight.yangtools.yang.data.impl.codec.CodecRegistry;
27 import org.osgi.framework.BundleContext;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
31 import javax.annotation.Nullable;
32 import javax.annotation.concurrent.GuardedBy;
33 import javax.management.DynamicMBean;
34 import javax.management.InstanceAlreadyExistsException;
35 import javax.management.InstanceNotFoundException;
36 import javax.management.MBeanServer;
37 import javax.management.ObjectName;
38 import java.util.ArrayList;
39 import java.util.Collection;
40 import java.util.HashSet;
41 import java.util.List;
43 import java.util.Map.Entry;
45 import java.util.concurrent.atomic.AtomicBoolean;
47 import static com.google.common.base.Preconditions.checkNotNull;
48 import static java.lang.String.format;
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 logger = 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 ServiceReferenceWritableRegistry writableSRRegistry;
87 public ConfigTransactionControllerImpl(ConfigTransactionLookupRegistry txLookupRegistry,
88 long parentVersion, CodecRegistry codecRegistry, long currentVersion,
89 Map<String, Entry<ModuleFactory, BundleContext>> currentlyRegisteredFactories,
90 MBeanServer transactionsMBeanServer, MBeanServer configMBeanServer,
91 boolean blankTransaction, ServiceReferenceWritableRegistry 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, codecRegistry);
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 Set<? extends Module> defaultModules = moduleFactory.getDefaultModules(dependencyResolverManager,
143 getModuleFactoryBundleContext(moduleFactory.getImplementationName()));
144 for (Module module : defaultModules) {
145 // ensure default module to be registered to jmx even if its module factory does not use dependencyResolverFactory
146 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(module.getIdentifier());
148 boolean defaultBean = true;
149 putConfigBeanToJMXAndInternalMaps(module.getIdentifier(), module, moduleFactory, null, dependencyResolver, defaultBean);
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(
167 ModuleInternalInfo oldConfigBeanInfo)
168 throws InstanceAlreadyExistsException {
169 transactionStatus.checkNotCommitStarted();
170 transactionStatus.checkNotAborted();
171 ModuleIdentifier moduleIdentifier = oldConfigBeanInfo.getIdentifier();
172 dependencyResolverManager.assertNotExists(moduleIdentifier);
174 ModuleFactory moduleFactory = factoriesHolder
175 .findByModuleName(moduleIdentifier.getFactoryName());
178 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(moduleIdentifier);
180 BundleContext bc = getModuleFactoryBundleContext(moduleFactory.getImplementationName());
181 module = moduleFactory.createModule(
182 moduleIdentifier.getInstanceName(), dependencyResolver,
183 oldConfigBeanInfo.getReadableModule(), bc);
184 } catch (Exception e) {
185 throw new IllegalStateException(format(
186 "Error while copying old configuration from %s to %s",
187 oldConfigBeanInfo, moduleFactory), e);
189 putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module, moduleFactory, oldConfigBeanInfo, dependencyResolver,
190 oldConfigBeanInfo.isDefaultBean());
194 public synchronized ObjectName createModule(String factoryName,
195 String instanceName) throws InstanceAlreadyExistsException {
197 transactionStatus.checkNotCommitStarted();
198 transactionStatus.checkNotAborted();
199 ModuleIdentifier moduleIdentifier = new ModuleIdentifier(factoryName, instanceName);
200 dependencyResolverManager.assertNotExists(moduleIdentifier);
203 ModuleFactory moduleFactory = factoriesHolder.findByModuleName(factoryName);
204 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(moduleIdentifier);
205 Module module = moduleFactory.createModule(instanceName, dependencyResolver,
206 getModuleFactoryBundleContext(moduleFactory.getImplementationName()));
207 boolean defaultBean = false;
208 return putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module,
209 moduleFactory, null, dependencyResolver, defaultBean);
212 private synchronized ObjectName putConfigBeanToJMXAndInternalMaps(
213 ModuleIdentifier moduleIdentifier, Module module,
214 ModuleFactory moduleFactory,
215 @Nullable ModuleInternalInfo maybeOldConfigBeanInfo, DependencyResolver dependencyResolver, boolean isDefaultBean)
216 throws InstanceAlreadyExistsException {
218 logger.debug("Adding module {} to transaction {}", moduleIdentifier, this);
219 if (moduleIdentifier.equals(module.getIdentifier()) == false) {
220 throw new IllegalStateException("Incorrect name reported by module. Expected "
221 + moduleIdentifier + ", got " + module.getIdentifier());
223 if (dependencyResolver.getIdentifier().equals(moduleIdentifier) == false) {
224 throw new IllegalStateException("Incorrect name reported by dependency resolver. Expected "
225 + moduleIdentifier + ", got " + dependencyResolver.getIdentifier());
227 DynamicMBean writableDynamicWrapper = new DynamicWritableWrapper(
228 module, moduleIdentifier, getTransactionIdentifier(),
229 readOnlyAtomicBoolean, transactionsMBeanServer,
232 ObjectName writableON = ObjectNameUtil.createTransactionModuleON(
233 getTransactionIdentifier().getName(), moduleIdentifier);
234 // put wrapper to jmx
235 TransactionModuleJMXRegistration transactionModuleJMXRegistration = getTxModuleJMXRegistrator()
236 .registerMBean(writableDynamicWrapper, writableON);
238 dependencyResolverManager.put(
239 moduleIdentifier, module, moduleFactory,
240 maybeOldConfigBeanInfo, transactionModuleJMXRegistration, isDefaultBean);
245 public synchronized void destroyModule(ObjectName objectName) throws InstanceNotFoundException {
246 checkTransactionName(objectName);
247 ObjectNameUtil.checkDomain(objectName);
248 ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(objectName,
249 ObjectNameUtil.TYPE_MODULE);
250 destroyModule(moduleIdentifier);
253 private void checkTransactionName(ObjectName objectName) {
254 String foundTransactionName = ObjectNameUtil
255 .getTransactionName(objectName);
256 if (getTransactionIdentifier().getName().equals(foundTransactionName) == false) {
257 throw new IllegalArgumentException("Wrong transaction name "
262 private synchronized void destroyModule(ModuleIdentifier moduleIdentifier) {
263 logger.debug("Destroying module {} in transaction {}", moduleIdentifier, this);
264 transactionStatus.checkNotAborted();
266 if (blankTransaction == false) {
267 ModuleInternalTransactionalInfo found =
268 dependencyResolverManager.findModuleInternalTransactionalInfo(moduleIdentifier);
269 if (found.isDefaultBean()) {
270 logger.warn("Warning: removing default bean. This will be forbidden in next version of config-subsystem");
273 // first remove refNames, it checks for objectname existence
275 writableSRRegistry.removeServiceReferences(
276 ObjectNameUtil.createTransactionModuleON(getTransactionName(), moduleIdentifier));
277 } catch (InstanceNotFoundException e) {
278 logger.error("Possible code error: cannot find {} in {}", moduleIdentifier, writableSRRegistry);
279 throw new IllegalStateException("Possible code error: cannot find " + moduleIdentifier, e);
282 ModuleInternalTransactionalInfo removedTInfo = dependencyResolverManager.destroyModule(moduleIdentifier);
284 removedTInfo.getTransactionModuleJMXRegistration().close();
288 public long getParentVersion() {
289 return parentVersion;
293 public long getVersion() {
294 return currentVersion;
298 public synchronized void validateConfig() throws ValidationException {
299 if (configBeanModificationDisabled.get()) {
300 throw new IllegalStateException("Cannot start validation");
302 configBeanModificationDisabled.set(true);
306 configBeanModificationDisabled.set(false);
310 private void validate_noLocks() throws ValidationException {
311 transactionStatus.checkNotAborted();
312 logger.trace("Validating transaction {}", getTransactionIdentifier());
314 List<ValidationException> collectedExceptions = new ArrayList<>();
315 for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
316 .getAllModules().entrySet()) {
317 ModuleIdentifier name = entry.getKey();
318 Module module = entry.getValue();
321 } catch (Exception e) {
322 logger.warn("Validation exception in {}", getTransactionName(),
324 collectedExceptions.add(ValidationException
325 .createForSingleException(name, e));
328 if (collectedExceptions.size() > 0) {
329 throw ValidationException
330 .createFromCollectedValidationExceptions(collectedExceptions);
332 logger.trace("Validated transaction {}", getTransactionIdentifier());
336 * If this method passes validation, it will grab
337 * {@link TransactionStatus#secondPhaseCommitStarted} lock. This lock will
338 * prevent calling @{link #validateBeforeCommitAndLockTransaction},
339 * effectively only allowing to call {@link #secondPhaseCommit} after
340 * successful return of this method.
343 public synchronized CommitInfo validateBeforeCommitAndLockTransaction()
344 throws ValidationException {
345 transactionStatus.checkNotAborted();
346 transactionStatus.checkNotCommitStarted();
347 configBeanModificationDisabled.set(true);
350 } catch (ValidationException e) {
351 logger.trace("Commit failed on validation");
352 configBeanModificationDisabled.set(false); // recoverable error
355 // errors in this state are not recoverable. modules are not mutable
357 transactionStatus.setSecondPhaseCommitStarted();
358 return dependencyResolverManager.toCommitInfo();
365 public synchronized List<ModuleIdentifier> secondPhaseCommit() {
366 transactionStatus.checkNotAborted();
367 transactionStatus.checkCommitStarted();
368 if (configBeanModificationDisabled.get() == false) {
369 throw new IllegalStateException(
370 "Internal error - validateBeforeCommitAndLockTransaction should be called "
371 + "to obtain a lock");
374 logger.trace("Committing transaction {}", getTransactionIdentifier());
376 // call getInstance()
377 for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
378 .getAllModules().entrySet()) {
379 Module module = entry.getValue();
380 ModuleIdentifier name = entry.getKey();
382 logger.debug("About to commit {} in transaction {}",
383 name, getTransactionIdentifier());
384 AutoCloseable instance = module.getInstance();
385 checkNotNull(instance, "Instance is null:{} in transaction {}", name, getTransactionIdentifier());
386 } catch (Exception e) {
387 logger.error("Commit failed on {} in transaction {}", name,
388 getTransactionIdentifier(), e);
390 throw new IllegalStateException(
391 format("Error - getInstance() failed for %s in transaction %s",
392 name, getTransactionIdentifier()), e);
396 // count dependency order
398 logger.trace("Committed configuration {}", getTransactionIdentifier());
399 transactionStatus.setCommitted();
401 return dependencyResolverManager.getSortedModuleIdentifiers();
405 public synchronized void abortConfig() {
406 transactionStatus.checkNotCommitStarted();
407 transactionStatus.checkNotAborted();
411 private void internalAbort() {
412 logger.trace("Aborting {}", this);
413 transactionStatus.setAborted();
417 public void close() {
418 dependencyResolverManager.close();
419 txLookupRegistry.close();
423 public ObjectName getControllerObjectName() {
428 public String getTransactionName() {
429 return getTransactionIdentifier().getName();
436 public Set<ObjectName> lookupConfigBeans() {
437 return txLookupRegistry.lookupConfigBeans();
444 public Set<ObjectName> lookupConfigBeans(String moduleName) {
445 return txLookupRegistry.lookupConfigBeans(moduleName);
452 public ObjectName lookupConfigBean(String moduleName, String instanceName)
453 throws InstanceNotFoundException {
454 return txLookupRegistry.lookupConfigBean(moduleName, instanceName);
461 public Set<ObjectName> lookupConfigBeans(String moduleName, String instanceName) {
462 return txLookupRegistry.lookupConfigBeans(moduleName, instanceName);
469 public void checkConfigBeanExists(ObjectName objectName) throws InstanceNotFoundException {
470 txLookupRegistry.checkConfigBeanExists(objectName);
478 public Set<String> getAvailableModuleNames() {
479 return factoriesHolder.getModuleNames();
483 public boolean isClosed() {
484 return transactionStatus.isAbortedOrCommitted();
488 public String toString() {
489 StringBuilder sb = new StringBuilder();
490 sb.append("transactionName=");
491 sb.append(getTransactionName());
492 return sb.toString();
495 // @VisibleForTesting
497 TransactionModuleJMXRegistrator getTxModuleJMXRegistrator() {
498 return txLookupRegistry.getTxModuleJMXRegistrator();
501 public TransactionIdentifier getName() {
502 return getTransactionIdentifier();
506 public List<ModuleFactory> getCurrentlyRegisteredFactories() {
507 return new ArrayList<>(factoriesHolder.getModuleFactories());
511 public TransactionIdentifier getIdentifier() {
512 return getTransactionIdentifier();
516 public BundleContext getModuleFactoryBundleContext(String factoryName) {
517 Map.Entry<ModuleFactory, BundleContext> factoryBundleContextEntry = this.currentlyRegisteredFactories.get(factoryName);
518 if (factoryBundleContextEntry == null || factoryBundleContextEntry.getValue() == null) {
519 throw new NullPointerException("Bundle context of " + factoryName + " ModuleFactory not found.");
521 return factoryBundleContextEntry.getValue();
524 // service reference functionality:
528 public synchronized ObjectName lookupConfigBeanByServiceInterfaceName(String serviceInterfaceQName, String refName) {
529 return writableSRRegistry.lookupConfigBeanByServiceInterfaceName(serviceInterfaceQName, refName);
533 public synchronized Map<String, Map<String, ObjectName>> getServiceMapping() {
534 return writableSRRegistry.getServiceMapping();
538 public synchronized Map<String, ObjectName> lookupServiceReferencesByServiceInterfaceName(String serviceInterfaceQName) {
539 return writableSRRegistry.lookupServiceReferencesByServiceInterfaceName(serviceInterfaceQName);
543 public synchronized Set<String> lookupServiceInterfaceNames(ObjectName objectName) throws InstanceNotFoundException {
544 return writableSRRegistry.lookupServiceInterfaceNames(objectName);
548 public synchronized String getServiceInterfaceName(String namespace, String localName) {
549 return writableSRRegistry.getServiceInterfaceName(namespace, localName);
553 public synchronized ObjectName saveServiceReference(String serviceInterfaceName, String refName, ObjectName moduleON) throws InstanceNotFoundException {
554 return writableSRRegistry.saveServiceReference(serviceInterfaceName, refName, moduleON);
558 public synchronized void removeServiceReference(String serviceInterfaceName, String refName) throws InstanceNotFoundException {
559 writableSRRegistry.removeServiceReference(serviceInterfaceName, refName);
563 public synchronized void removeAllServiceReferences() {
564 writableSRRegistry.removeAllServiceReferences();
568 public boolean removeServiceReferences(ObjectName objectName) throws InstanceNotFoundException {
569 return writableSRRegistry.removeServiceReferences(objectName);
573 public ServiceReferenceWritableRegistry getWritableRegistry() {
574 return writableSRRegistry;
578 public TransactionIdentifier getTransactionIdentifier() {
579 return txLookupRegistry.getTransactionIdentifier();
583 public Set<String> getAvailableModuleFactoryQNames() {
584 return txLookupRegistry.getAvailableModuleFactoryQNames();
588 public void checkServiceReferenceExists(ObjectName objectName) throws InstanceNotFoundException {
589 writableSRRegistry.checkServiceReferenceExists(objectName);
593 public ObjectName getServiceReference(String serviceInterfaceQName, String refName) throws InstanceNotFoundException {
594 return writableSRRegistry.getServiceReference(serviceInterfaceQName, refName);