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.ValidationException;
13 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
14 import org.opendaylight.controller.config.manager.impl.dependencyresolver.DependencyResolverManager;
15 import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicWritableWrapper;
16 import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean;
17 import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean.ReadOnlyAtomicBooleanImpl;
18 import org.opendaylight.controller.config.manager.impl.factoriesresolver.HierarchicalConfigMBeanFactoriesHolder;
19 import org.opendaylight.controller.config.manager.impl.jmx.TransactionJMXRegistrator;
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.manager.impl.util.LookupBeansUtil;
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.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.HashSet;
38 import java.util.List;
40 import java.util.Map.Entry;
42 import java.util.ArrayList;
43 import java.util.Collection;
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 * {@link #transactionIdentifier}, 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 TransactionIdentifier transactionIdentifier;
60 private final ObjectName controllerON;
61 private final TransactionJMXRegistrator transactionRegistrator;
62 private final TransactionModuleJMXRegistrator txModuleJMXRegistrator;
63 private final long parentVersion, currentVersion;
64 private final HierarchicalConfigMBeanFactoriesHolder factoriesHolder;
65 private final DependencyResolverManager dependencyResolverManager;
66 private final TransactionStatus transactionStatus;
67 private final MBeanServer transactionsMBeanServer;
68 private final Map<String, Map.Entry<ModuleFactory, BundleContext>> currentlyRegisteredFactories;
71 * Disables ability of {@link DynamicWritableWrapper} to change attributes
75 private final AtomicBoolean configBeanModificationDisabled = new AtomicBoolean(
77 private final ReadOnlyAtomicBoolean readOnlyAtomicBoolean = new ReadOnlyAtomicBooleanImpl(
78 configBeanModificationDisabled);
79 private final MBeanServer configMBeanServer;
81 private final boolean blankTransaction;
83 public ConfigTransactionControllerImpl(String transactionName,
84 TransactionJMXRegistrator transactionRegistrator,
85 long parentVersion, long currentVersion,
86 Map<String, Map.Entry<ModuleFactory, BundleContext>> currentlyRegisteredFactories,
87 MBeanServer transactionsMBeanServer, MBeanServer configMBeanServer,
88 boolean blankTransaction) {
90 this.transactionIdentifier = new TransactionIdentifier(transactionName);
91 this.controllerON = ObjectNameUtil
92 .createTransactionControllerON(transactionName);
93 this.transactionRegistrator = transactionRegistrator;
94 txModuleJMXRegistrator = transactionRegistrator
95 .createTransactionModuleJMXRegistrator();
96 this.parentVersion = parentVersion;
97 this.currentVersion = currentVersion;
98 this.currentlyRegisteredFactories = currentlyRegisteredFactories;
99 this.factoriesHolder = new HierarchicalConfigMBeanFactoriesHolder(currentlyRegisteredFactories);
100 this.transactionStatus = new TransactionStatus();
101 this.dependencyResolverManager = new DependencyResolverManager(transactionName, transactionStatus);
102 this.transactionsMBeanServer = transactionsMBeanServer;
103 this.configMBeanServer = configMBeanServer;
104 this.blankTransaction = blankTransaction;
108 public void copyExistingModulesAndProcessFactoryDiff(Collection<ModuleInternalInfo> existingModules, List<ModuleFactory> lastListOfFactories) {
109 // copy old configuration to this server
110 for (ModuleInternalInfo oldConfigInfo : existingModules) {
112 copyExistingModule(oldConfigInfo);
113 } catch (InstanceAlreadyExistsException e) {
114 throw new IllegalStateException("Error while copying " + oldConfigInfo, e);
117 processDefaultBeans(lastListOfFactories);
120 private synchronized void processDefaultBeans(List<ModuleFactory> lastListOfFactories) {
121 transactionStatus.checkNotCommitStarted();
122 transactionStatus.checkNotAborted();
124 Set<ModuleFactory> oldSet = new HashSet<>(lastListOfFactories);
125 Set<ModuleFactory> newSet = new HashSet<>(factoriesHolder.getModuleFactories());
127 List<ModuleFactory> toBeAdded = new ArrayList<>();
128 List<ModuleFactory> toBeRemoved = new ArrayList<>();
129 for(ModuleFactory moduleFactory: factoriesHolder.getModuleFactories()) {
130 if (oldSet.contains(moduleFactory) == false){
131 toBeAdded.add(moduleFactory);
134 for(ModuleFactory moduleFactory: lastListOfFactories){
135 if (newSet.contains(moduleFactory) == false) {
136 toBeRemoved.add(moduleFactory);
139 // add default modules
140 for (ModuleFactory moduleFactory : toBeAdded) {
141 Set<? extends Module> defaultModules = moduleFactory.getDefaultModules(dependencyResolverManager,
142 getModuleFactoryBundleContext(moduleFactory.getImplementationName()));
143 for (Module module : defaultModules) {
144 // ensure default module to be registered to jmx even if its module factory does not use dependencyResolverFactory
145 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(module.getIdentifier());
147 boolean defaultBean = true;
148 putConfigBeanToJMXAndInternalMaps(module.getIdentifier(), module, moduleFactory, null, dependencyResolver, defaultBean);
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(
166 ModuleInternalInfo oldConfigBeanInfo)
167 throws InstanceAlreadyExistsException {
168 transactionStatus.checkNotCommitStarted();
169 transactionStatus.checkNotAborted();
170 ModuleIdentifier moduleIdentifier = oldConfigBeanInfo.getName();
171 dependencyResolverManager.assertNotExists(moduleIdentifier);
173 ModuleFactory moduleFactory = factoriesHolder
174 .findByModuleName(moduleIdentifier.getFactoryName());
177 DependencyResolver dependencyResolver = dependencyResolverManager
178 .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, transactionIdentifier,
229 readOnlyAtomicBoolean, transactionsMBeanServer,
232 ObjectName writableON = ObjectNameUtil.createTransactionModuleON(
233 transactionIdentifier.getName(), moduleIdentifier);
234 // put wrapper to jmx
235 TransactionModuleJMXRegistration transactionModuleJMXRegistration = txModuleJMXRegistrator
236 .registerMBean(writableDynamicWrapper, writableON);
237 ModuleInternalTransactionalInfo moduleInternalTransactionalInfo = new ModuleInternalTransactionalInfo(
238 moduleIdentifier, module, moduleFactory,
239 maybeOldConfigBeanInfo, transactionModuleJMXRegistration, isDefaultBean);
241 dependencyResolverManager.put(moduleInternalTransactionalInfo);
246 public synchronized void destroyModule(ObjectName objectName)
247 throws InstanceNotFoundException {
248 String foundTransactionName = ObjectNameUtil
249 .getTransactionName(objectName);
250 if (transactionIdentifier.getName().equals(foundTransactionName) == false) {
251 throw new IllegalArgumentException("Wrong transaction name "
254 ObjectNameUtil.checkDomain(objectName);
255 ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(objectName,
256 ObjectNameUtil.TYPE_MODULE);
257 destroyModule(moduleIdentifier);
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 ModuleInternalTransactionalInfo removedTInfo = dependencyResolverManager.destroyModule(moduleIdentifier);
273 removedTInfo.getTransactionModuleJMXRegistration().close();
277 public long getParentVersion() {
278 return parentVersion;
282 public long getVersion() {
283 return currentVersion;
287 public synchronized void validateConfig() throws ValidationException {
288 if (configBeanModificationDisabled.get())
289 throw new IllegalStateException("Cannot start validation");
290 configBeanModificationDisabled.set(true);
294 configBeanModificationDisabled.set(false);
298 private void validate_noLocks() throws ValidationException {
299 transactionStatus.checkNotAborted();
300 logger.info("Validating transaction {}", transactionIdentifier);
302 List<ValidationException> collectedExceptions = new ArrayList<>();
303 for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
304 .getAllModules().entrySet()) {
305 ModuleIdentifier name = entry.getKey();
306 Module module = entry.getValue();
309 } catch (Exception e) {
310 logger.warn("Validation exception in {}", getTransactionName(),
312 collectedExceptions.add(ValidationException
313 .createForSingleException(name, e));
316 if (collectedExceptions.size() > 0) {
317 throw ValidationException
318 .createFromCollectedValidationExceptions(collectedExceptions);
320 logger.info("Validated transaction {}", transactionIdentifier);
324 * If this method passes validation, it will grab
325 * {@link TransactionStatus#secondPhaseCommitStarted} lock. This lock will
326 * prevent calling @{link #validateBeforeCommitAndLockTransaction},
327 * effectively only allowing to call {@link #secondPhaseCommit} after
328 * successful return of this method.
331 public synchronized CommitInfo validateBeforeCommitAndLockTransaction()
332 throws ValidationException {
333 transactionStatus.checkNotAborted();
334 transactionStatus.checkNotCommitStarted();
335 configBeanModificationDisabled.set(true);
338 } catch (ValidationException e) {
339 logger.info("Commit failed on validation");
340 configBeanModificationDisabled.set(false); // recoverable error
343 // errors in this state are not recoverable. modules are not mutable
345 transactionStatus.setSecondPhaseCommitStarted();
346 return dependencyResolverManager.toCommitInfo();
353 public synchronized List<ModuleIdentifier> secondPhaseCommit() {
354 transactionStatus.checkNotAborted();
355 transactionStatus.checkCommitStarted();
356 if (configBeanModificationDisabled.get() == false) {
357 throw new IllegalStateException(
358 "Internal error - validateBeforeCommitAndLockTransaction should be called "
359 + "to obtain a lock");
362 logger.info("Committing transaction {}", transactionIdentifier);
364 // call getInstance()
365 for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
366 .getAllModules().entrySet()) {
367 Module module = entry.getValue();
368 ModuleIdentifier name = entry.getKey();
370 logger.debug("About to commit {} in transaction {}",
371 name, transactionIdentifier);
372 module.getInstance();
373 } catch (Exception e) {
374 logger.error("Commit failed on {} in transaction {}", name,
375 transactionIdentifier, e);
377 throw new RuntimeException(
378 format("Error - getInstance() failed for %s in transaction %s",
379 name, transactionIdentifier), e);
383 // count dependency order
385 logger.info("Committed configuration {}", transactionIdentifier);
386 transactionStatus.setCommitted();
387 // unregister this and all modules from jmx
390 return dependencyResolverManager.getSortedModuleIdentifiers();
394 public synchronized void abortConfig() {
395 transactionStatus.checkNotCommitStarted();
396 transactionStatus.checkNotAborted();
400 private void internalAbort() {
401 transactionStatus.setAborted();
405 private void close() {
406 transactionRegistrator.close();
410 public ObjectName getControllerObjectName() {
415 public String getTransactionName() {
416 return transactionIdentifier.getName();
423 public Set<ObjectName> lookupConfigBeans() {
424 return lookupConfigBeans("*", "*");
431 public Set<ObjectName> lookupConfigBeans(String moduleName) {
432 return lookupConfigBeans(moduleName, "*");
439 public ObjectName lookupConfigBean(String moduleName, String instanceName)
440 throws InstanceNotFoundException {
441 return LookupBeansUtil.lookupConfigBean(this, moduleName, instanceName);
448 public Set<ObjectName> lookupConfigBeans(String moduleName,
449 String instanceName) {
450 ObjectName namePattern = ObjectNameUtil.createModulePattern(moduleName,
451 instanceName, transactionIdentifier.getName());
452 return txModuleJMXRegistrator.queryNames(namePattern, null);
456 public Set<String> getAvailableModuleNames() {
457 return factoriesHolder.getModuleNames();
461 public boolean isClosed() {
462 return transactionStatus.isAbortedOrCommitted();
466 public String toString() {
467 StringBuilder sb = new StringBuilder();
468 sb.append("transactionName=");
469 sb.append(getTransactionName());
470 return sb.toString();
473 // @VisibleForTesting
475 TransactionModuleJMXRegistrator getTxModuleJMXRegistrator() {
476 return txModuleJMXRegistrator;
479 public TransactionIdentifier getName() {
480 return transactionIdentifier;
484 public List<ModuleFactory> getCurrentlyRegisteredFactories() {
485 return new ArrayList<>(factoriesHolder.getModuleFactories());
489 public TransactionIdentifier getIdentifier() {
490 return transactionIdentifier;
494 public BundleContext getModuleFactoryBundleContext(String factoryName) {
495 Map.Entry<ModuleFactory, BundleContext> factoryBundleContextEntry = this.currentlyRegisteredFactories.get(factoryName);
496 if (factoryBundleContextEntry == null || factoryBundleContextEntry.getValue() == null) {
497 throw new NullPointerException("Bundle context of " + factoryName + " ModuleFactory not found.");
499 return factoryBundleContextEntry.getValue();