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.dynamicmbean.DynamicWritableWrapper;
17 import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean;
18 import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean.ReadOnlyAtomicBooleanImpl;
19 import org.opendaylight.controller.config.manager.impl.factoriesresolver.HierarchicalConfigMBeanFactoriesHolder;
20 import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator;
21 import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator.TransactionModuleJMXRegistration;
22 import org.opendaylight.controller.config.spi.Module;
23 import org.opendaylight.controller.config.spi.ModuleFactory;
24 import org.opendaylight.yangtools.concepts.Identifiable;
25 import org.opendaylight.yangtools.yang.data.impl.codec.CodecRegistry;
26 import org.osgi.framework.BundleContext;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
30 import javax.annotation.Nullable;
31 import javax.annotation.concurrent.GuardedBy;
32 import javax.management.DynamicMBean;
33 import javax.management.InstanceAlreadyExistsException;
34 import javax.management.InstanceNotFoundException;
35 import javax.management.MBeanServer;
36 import javax.management.ObjectName;
37 import java.util.ArrayList;
38 import java.util.Collection;
39 import java.util.HashSet;
40 import java.util.List;
42 import java.util.Map.Entry;
44 import java.util.concurrent.atomic.AtomicBoolean;
46 import static java.lang.String.format;
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 logger = 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 ServiceReferenceWritableRegistry 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, ServiceReferenceWritableRegistry 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(transactionName, transactionStatus, writableSRRegistry, codecRegistry);
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 Set<? extends Module> defaultModules = moduleFactory.getDefaultModules(dependencyResolverManager,
140 getModuleFactoryBundleContext(moduleFactory.getImplementationName()));
141 for (Module module : defaultModules) {
142 // ensure default module to be registered to jmx even if its module factory does not use dependencyResolverFactory
143 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(module.getIdentifier());
145 boolean defaultBean = true;
146 putConfigBeanToJMXAndInternalMaps(module.getIdentifier(), module, moduleFactory, null, dependencyResolver, defaultBean);
147 } catch (InstanceAlreadyExistsException e) {
148 throw new IllegalStateException(e);
153 // remove modules belonging to removed factories
154 for(ModuleFactory removedFactory: toBeRemoved){
155 List<ModuleIdentifier> modulesOfRemovedFactory = dependencyResolverManager.findAllByFactory(removedFactory);
156 for (ModuleIdentifier name : modulesOfRemovedFactory) {
163 private synchronized void copyExistingModule(
164 ModuleInternalInfo oldConfigBeanInfo)
165 throws InstanceAlreadyExistsException {
166 transactionStatus.checkNotCommitStarted();
167 transactionStatus.checkNotAborted();
168 ModuleIdentifier moduleIdentifier = oldConfigBeanInfo.getIdentifier();
169 dependencyResolverManager.assertNotExists(moduleIdentifier);
171 ModuleFactory moduleFactory = factoriesHolder
172 .findByModuleName(moduleIdentifier.getFactoryName());
175 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(moduleIdentifier);
177 BundleContext bc = getModuleFactoryBundleContext(moduleFactory.getImplementationName());
178 module = moduleFactory.createModule(
179 moduleIdentifier.getInstanceName(), dependencyResolver,
180 oldConfigBeanInfo.getReadableModule(), bc);
181 } catch (Exception e) {
182 throw new IllegalStateException(format(
183 "Error while copying old configuration from %s to %s",
184 oldConfigBeanInfo, moduleFactory), e);
186 putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module, moduleFactory, oldConfigBeanInfo, dependencyResolver,
187 oldConfigBeanInfo.isDefaultBean());
191 public synchronized ObjectName createModule(String factoryName,
192 String instanceName) throws InstanceAlreadyExistsException {
194 transactionStatus.checkNotCommitStarted();
195 transactionStatus.checkNotAborted();
196 ModuleIdentifier moduleIdentifier = new ModuleIdentifier(factoryName, instanceName);
197 dependencyResolverManager.assertNotExists(moduleIdentifier);
200 ModuleFactory moduleFactory = factoriesHolder.findByModuleName(factoryName);
201 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(moduleIdentifier);
202 Module module = moduleFactory.createModule(instanceName, dependencyResolver,
203 getModuleFactoryBundleContext(moduleFactory.getImplementationName()));
204 boolean defaultBean = false;
205 return putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module,
206 moduleFactory, null, dependencyResolver, defaultBean);
209 private synchronized ObjectName putConfigBeanToJMXAndInternalMaps(
210 ModuleIdentifier moduleIdentifier, Module module,
211 ModuleFactory moduleFactory,
212 @Nullable ModuleInternalInfo maybeOldConfigBeanInfo, DependencyResolver dependencyResolver, boolean isDefaultBean)
213 throws InstanceAlreadyExistsException {
215 logger.debug("Adding module {} to transaction {}", moduleIdentifier, this);
216 if (moduleIdentifier.equals(module.getIdentifier())==false) {
217 throw new IllegalStateException("Incorrect name reported by module. Expected "
218 + moduleIdentifier + ", got " + module.getIdentifier());
220 if (dependencyResolver.getIdentifier().equals(moduleIdentifier) == false ) {
221 throw new IllegalStateException("Incorrect name reported by dependency resolver. Expected "
222 + moduleIdentifier + ", got " + dependencyResolver.getIdentifier());
224 DynamicMBean writableDynamicWrapper = new DynamicWritableWrapper(
225 module, moduleIdentifier, getTransactionIdentifier(),
226 readOnlyAtomicBoolean, transactionsMBeanServer,
229 ObjectName writableON = ObjectNameUtil.createTransactionModuleON(
230 getTransactionIdentifier().getName(), moduleIdentifier);
231 // put wrapper to jmx
232 TransactionModuleJMXRegistration transactionModuleJMXRegistration = getTxModuleJMXRegistrator()
233 .registerMBean(writableDynamicWrapper, writableON);
234 ModuleInternalTransactionalInfo moduleInternalTransactionalInfo = new ModuleInternalTransactionalInfo(
235 moduleIdentifier, module, moduleFactory,
236 maybeOldConfigBeanInfo, transactionModuleJMXRegistration, isDefaultBean);
238 dependencyResolverManager.put(moduleInternalTransactionalInfo);
243 public synchronized void destroyModule(ObjectName objectName) throws InstanceNotFoundException {
244 checkTransactionName(objectName);
245 ObjectNameUtil.checkDomain(objectName);
246 ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(objectName,
247 ObjectNameUtil.TYPE_MODULE);
248 destroyModule(moduleIdentifier);
251 private void checkTransactionName(ObjectName objectName) {
252 String foundTransactionName = ObjectNameUtil
253 .getTransactionName(objectName);
254 if (getTransactionIdentifier().getName().equals(foundTransactionName) == false) {
255 throw new IllegalArgumentException("Wrong transaction name "
260 private synchronized void destroyModule(ModuleIdentifier moduleIdentifier) {
261 logger.debug("Destroying module {} in transaction {}", moduleIdentifier, this);
262 transactionStatus.checkNotAborted();
264 if (blankTransaction == false) {
265 ModuleInternalTransactionalInfo found =
266 dependencyResolverManager.findModuleInternalTransactionalInfo(moduleIdentifier);
267 if (found.isDefaultBean()) {
268 logger.warn("Warning: removing default bean. This will be forbidden in next version of config-subsystem");
271 // first remove refNames, it checks for objectname existence
273 writableSRRegistry.removeServiceReferences(
274 ObjectNameUtil.createTransactionModuleON(getTransactionName(),moduleIdentifier));
275 } catch (InstanceNotFoundException e) {
276 logger.error("Possible code error: cannot find {} in {}", moduleIdentifier, writableSRRegistry);
277 throw new IllegalStateException("Possible code error: cannot find " + moduleIdentifier, e);
280 ModuleInternalTransactionalInfo removedTInfo = dependencyResolverManager.destroyModule(moduleIdentifier);
282 removedTInfo.getTransactionModuleJMXRegistration().close();
286 public long getParentVersion() {
287 return parentVersion;
291 public long getVersion() {
292 return currentVersion;
296 public synchronized void validateConfig() throws ValidationException {
297 if (configBeanModificationDisabled.get())
298 throw new IllegalStateException("Cannot start validation");
299 configBeanModificationDisabled.set(true);
303 configBeanModificationDisabled.set(false);
307 private void validate_noLocks() throws ValidationException {
308 transactionStatus.checkNotAborted();
309 logger.trace("Validating transaction {}", getTransactionIdentifier());
311 List<ValidationException> collectedExceptions = new ArrayList<>();
312 for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
313 .getAllModules().entrySet()) {
314 ModuleIdentifier name = entry.getKey();
315 Module module = entry.getValue();
318 } catch (Exception e) {
319 logger.warn("Validation exception in {}", getTransactionName(),
321 collectedExceptions.add(ValidationException
322 .createForSingleException(name, e));
325 if (collectedExceptions.size() > 0) {
326 throw ValidationException
327 .createFromCollectedValidationExceptions(collectedExceptions);
329 logger.trace("Validated transaction {}", getTransactionIdentifier());
333 * If this method passes validation, it will grab
334 * {@link TransactionStatus#secondPhaseCommitStarted} lock. This lock will
335 * prevent calling @{link #validateBeforeCommitAndLockTransaction},
336 * effectively only allowing to call {@link #secondPhaseCommit} after
337 * successful return of this method.
340 public synchronized CommitInfo validateBeforeCommitAndLockTransaction()
341 throws ValidationException {
342 transactionStatus.checkNotAborted();
343 transactionStatus.checkNotCommitStarted();
344 configBeanModificationDisabled.set(true);
347 } catch (ValidationException e) {
348 logger.trace("Commit failed on validation");
349 configBeanModificationDisabled.set(false); // recoverable error
352 // errors in this state are not recoverable. modules are not mutable
354 transactionStatus.setSecondPhaseCommitStarted();
355 return dependencyResolverManager.toCommitInfo();
362 public synchronized List<ModuleIdentifier> secondPhaseCommit() {
363 transactionStatus.checkNotAborted();
364 transactionStatus.checkCommitStarted();
365 if (configBeanModificationDisabled.get() == false) {
366 throw new IllegalStateException(
367 "Internal error - validateBeforeCommitAndLockTransaction should be called "
368 + "to obtain a lock");
371 logger.trace("Committing transaction {}", getTransactionIdentifier());
373 // call getInstance()
374 for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
375 .getAllModules().entrySet()) {
376 Module module = entry.getValue();
377 ModuleIdentifier name = entry.getKey();
379 logger.debug("About to commit {} in transaction {}",
380 name, getTransactionIdentifier());
381 module.getInstance();
382 } catch (Exception e) {
383 logger.error("Commit failed on {} in transaction {}", name,
384 getTransactionIdentifier(), e);
386 throw new RuntimeException(
387 format("Error - getInstance() failed for %s in transaction %s",
388 name, getTransactionIdentifier()), e);
392 // count dependency order
394 logger.trace("Committed configuration {}", getTransactionIdentifier());
395 transactionStatus.setCommitted();
396 // unregister this and all modules from jmx
399 return dependencyResolverManager.getSortedModuleIdentifiers();
403 public synchronized void abortConfig() {
404 transactionStatus.checkNotCommitStarted();
405 transactionStatus.checkNotAborted();
409 private void internalAbort() {
410 transactionStatus.setAborted();
414 public void close() {
415 //FIXME: should not close object that was retrieved in constructor, a wrapper object should do that perhaps
416 txLookupRegistry.close();
420 public ObjectName getControllerObjectName() {
425 public String getTransactionName() {
426 return getTransactionIdentifier().getName();
433 public Set<ObjectName> lookupConfigBeans() {
434 return txLookupRegistry.lookupConfigBeans();
441 public Set<ObjectName> lookupConfigBeans(String moduleName) {
442 return txLookupRegistry.lookupConfigBeans(moduleName);
449 public ObjectName lookupConfigBean(String moduleName, String instanceName)
450 throws InstanceNotFoundException {
451 return txLookupRegistry.lookupConfigBean(moduleName, instanceName);
458 public Set<ObjectName> lookupConfigBeans(String moduleName, String instanceName) {
459 return txLookupRegistry.lookupConfigBeans(moduleName, instanceName);
466 public void checkConfigBeanExists(ObjectName objectName) throws InstanceNotFoundException {
467 txLookupRegistry.checkConfigBeanExists(objectName);
475 public Set<String> getAvailableModuleNames() {
476 return factoriesHolder.getModuleNames();
480 public boolean isClosed() {
481 return transactionStatus.isAbortedOrCommitted();
485 public String toString() {
486 StringBuilder sb = new StringBuilder();
487 sb.append("transactionName=");
488 sb.append(getTransactionName());
489 return sb.toString();
492 // @VisibleForTesting
494 TransactionModuleJMXRegistrator getTxModuleJMXRegistrator() {
495 return txLookupRegistry.getTxModuleJMXRegistrator();
498 public TransactionIdentifier getName() {
499 return getTransactionIdentifier();
503 public List<ModuleFactory> getCurrentlyRegisteredFactories() {
504 return new ArrayList<>(factoriesHolder.getModuleFactories());
508 public TransactionIdentifier getIdentifier() {
509 return getTransactionIdentifier();
513 public BundleContext getModuleFactoryBundleContext(String factoryName) {
514 Map.Entry<ModuleFactory, BundleContext> factoryBundleContextEntry = this.currentlyRegisteredFactories.get(factoryName);
515 if (factoryBundleContextEntry == null || factoryBundleContextEntry.getValue() == null) {
516 throw new NullPointerException("Bundle context of " + factoryName + " ModuleFactory not found.");
518 return factoryBundleContextEntry.getValue();
521 // service reference functionality:
525 public synchronized ObjectName lookupConfigBeanByServiceInterfaceName(String serviceInterfaceQName, String refName) {
526 return writableSRRegistry.lookupConfigBeanByServiceInterfaceName(serviceInterfaceQName, refName);
530 public synchronized Map<String, Map<String, ObjectName>> getServiceMapping() {
531 return writableSRRegistry.getServiceMapping();
535 public synchronized Map<String, ObjectName> lookupServiceReferencesByServiceInterfaceName(String serviceInterfaceQName) {
536 return writableSRRegistry.lookupServiceReferencesByServiceInterfaceName(serviceInterfaceQName);
540 public synchronized Set<String> lookupServiceInterfaceNames(ObjectName objectName) throws InstanceNotFoundException {
541 return writableSRRegistry.lookupServiceInterfaceNames(objectName);
545 public synchronized String getServiceInterfaceName(String namespace, String localName) {
546 return writableSRRegistry.getServiceInterfaceName(namespace, localName);
550 public synchronized ObjectName saveServiceReference(String serviceInterfaceName, String refName, ObjectName moduleON) throws InstanceNotFoundException {
551 return writableSRRegistry.saveServiceReference(serviceInterfaceName, refName, moduleON);
555 public synchronized void removeServiceReference(String serviceInterfaceName, String refName) throws InstanceNotFoundException {
556 writableSRRegistry.removeServiceReference(serviceInterfaceName, refName);
560 public synchronized void removeAllServiceReferences() {
561 writableSRRegistry.removeAllServiceReferences();
565 public boolean removeServiceReferences(ObjectName objectName) throws InstanceNotFoundException {
566 return writableSRRegistry.removeServiceReferences(objectName);
570 public ServiceReferenceWritableRegistry getWritableRegistry() {
571 return writableSRRegistry;
574 public TransactionIdentifier getTransactionIdentifier() {
575 return txLookupRegistry.getTransactionIdentifier();
579 public Set<String> getAvailableModuleFactoryQNames() {
580 return txLookupRegistry.getAvailableModuleFactoryQNames();
584 public void checkServiceReferenceExists(ObjectName objectName) throws InstanceNotFoundException {
585 writableSRRegistry.checkServiceReferenceExists(objectName);
589 public ObjectName getServiceReference(String serviceInterfaceQName, String refName) throws InstanceNotFoundException {
590 return writableSRRegistry.getServiceReference(serviceInterfaceQName, refName);