Bug 116 - Revisit SanityTest
[controller.git] / opendaylight / config / config-manager / src / main / java / org / opendaylight / controller / config / manager / impl / ConfigTransactionControllerImpl.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.config.manager.impl;
9
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;
29
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;
39 import java.util.Map;
40 import java.util.Map.Entry;
41 import java.util.Set;
42 import java.util.ArrayList;
43 import java.util.Collection;
44 import java.util.concurrent.atomic.AtomicBoolean;
45
46 import static java.lang.String.format;
47
48 /**
49  * This is a JMX bean representing current transaction. It contains
50  * {@link #transactionIdentifier}, unique version and parent version for
51  * optimistic locking.
52  */
53 class ConfigTransactionControllerImpl implements
54         ConfigTransactionControllerInternal,
55         ConfigTransactionControllerImplMXBean,
56         Identifiable<TransactionIdentifier>{
57     private static final Logger logger = LoggerFactory.getLogger(ConfigTransactionControllerImpl.class);
58
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;
69
70     /**
71      * Disables ability of {@link DynamicWritableWrapper} to change attributes
72      * during validation.
73      */
74     @GuardedBy("this")
75     private final AtomicBoolean configBeanModificationDisabled = new AtomicBoolean(
76             false);
77     private final ReadOnlyAtomicBoolean readOnlyAtomicBoolean = new ReadOnlyAtomicBooleanImpl(
78             configBeanModificationDisabled);
79     private final MBeanServer configMBeanServer;
80
81     private final boolean blankTransaction;
82
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) {
89
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;
105     }
106
107     @Override
108     public void copyExistingModulesAndProcessFactoryDiff(Collection<ModuleInternalInfo> existingModules, List<ModuleFactory> lastListOfFactories) {
109         // copy old configuration to this server
110         for (ModuleInternalInfo oldConfigInfo : existingModules) {
111             try {
112                 copyExistingModule(oldConfigInfo);
113             } catch (InstanceAlreadyExistsException e) {
114                 throw new IllegalStateException("Error while copying " + oldConfigInfo, e);
115             }
116         }
117         processDefaultBeans(lastListOfFactories);
118     }
119
120     private synchronized void processDefaultBeans(List<ModuleFactory> lastListOfFactories) {
121         transactionStatus.checkNotCommitStarted();
122         transactionStatus.checkNotAborted();
123
124         Set<ModuleFactory> oldSet = new HashSet<>(lastListOfFactories);
125         Set<ModuleFactory> newSet = new HashSet<>(factoriesHolder.getModuleFactories());
126
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);
132             }
133         }
134         for(ModuleFactory moduleFactory: lastListOfFactories){
135             if (newSet.contains(moduleFactory) == false) {
136                 toBeRemoved.add(moduleFactory);
137             }
138         }
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());
146                 try {
147                     boolean defaultBean = true;
148                     putConfigBeanToJMXAndInternalMaps(module.getIdentifier(), module, moduleFactory, null, dependencyResolver, defaultBean);
149                 } catch (InstanceAlreadyExistsException e) {
150                     throw new IllegalStateException(e);
151                 }
152             }
153         }
154
155         // remove modules belonging to removed factories
156         for(ModuleFactory removedFactory: toBeRemoved){
157             List<ModuleIdentifier> modulesOfRemovedFactory = dependencyResolverManager.findAllByFactory(removedFactory);
158             for (ModuleIdentifier name : modulesOfRemovedFactory) {
159                 destroyModule(name);
160             }
161         }
162     }
163
164
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);
172
173         ModuleFactory moduleFactory = factoriesHolder
174                 .findByModuleName(moduleIdentifier.getFactoryName());
175
176         Module module;
177         DependencyResolver dependencyResolver = dependencyResolverManager
178                 .getOrCreate(moduleIdentifier);
179         try {
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);
188         }
189         putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module, moduleFactory, oldConfigBeanInfo, dependencyResolver,
190                 oldConfigBeanInfo.isDefaultBean());
191     }
192
193     @Override
194     public synchronized ObjectName createModule(String factoryName,
195             String instanceName) throws InstanceAlreadyExistsException {
196
197         transactionStatus.checkNotCommitStarted();
198         transactionStatus.checkNotAborted();
199         ModuleIdentifier moduleIdentifier = new ModuleIdentifier(factoryName, instanceName);
200         dependencyResolverManager.assertNotExists(moduleIdentifier);
201
202         // find factory
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);
210     }
211
212     private synchronized ObjectName putConfigBeanToJMXAndInternalMaps(
213             ModuleIdentifier moduleIdentifier, Module module,
214             ModuleFactory moduleFactory,
215             @Nullable ModuleInternalInfo maybeOldConfigBeanInfo, DependencyResolver dependencyResolver, boolean isDefaultBean)
216             throws InstanceAlreadyExistsException {
217
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());
222         }
223         if (dependencyResolver.getIdentifier().equals(moduleIdentifier) == false ) {
224             throw new IllegalStateException("Incorrect name reported by dependency resolver. Expected "
225                     + moduleIdentifier + ", got " + dependencyResolver.getIdentifier());
226         }
227         DynamicMBean writableDynamicWrapper = new DynamicWritableWrapper(
228                 module, moduleIdentifier, transactionIdentifier,
229                 readOnlyAtomicBoolean, transactionsMBeanServer,
230                 configMBeanServer);
231
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);
240
241         dependencyResolverManager.put(moduleInternalTransactionalInfo);
242         return writableON;
243     }
244
245     @Override
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 "
252                     + objectName);
253         }
254         ObjectNameUtil.checkDomain(objectName);
255         ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(objectName,
256                 ObjectNameUtil.TYPE_MODULE);
257         destroyModule(moduleIdentifier);
258     }
259
260     private synchronized void destroyModule(ModuleIdentifier moduleIdentifier) {
261         logger.debug("Destroying module {} in transaction {}", moduleIdentifier, this);
262         transactionStatus.checkNotAborted();
263
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");
269             }
270         }
271         ModuleInternalTransactionalInfo removedTInfo = dependencyResolverManager.destroyModule(moduleIdentifier);
272         // remove from jmx
273         removedTInfo.getTransactionModuleJMXRegistration().close();
274     }
275
276     @Override
277     public long getParentVersion() {
278         return parentVersion;
279     }
280
281     @Override
282     public long getVersion() {
283         return currentVersion;
284     }
285
286     @Override
287     public synchronized void validateConfig() throws ValidationException {
288         if (configBeanModificationDisabled.get())
289             throw new IllegalStateException("Cannot start validation");
290         configBeanModificationDisabled.set(true);
291         try {
292             validate_noLocks();
293         } finally {
294             configBeanModificationDisabled.set(false);
295         }
296     }
297
298     private void validate_noLocks() throws ValidationException {
299         transactionStatus.checkNotAborted();
300         logger.info("Validating transaction {}", transactionIdentifier);
301         // call validate()
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();
307             try {
308                 module.validate();
309             } catch (Exception e) {
310                 logger.warn("Validation exception in {}", getTransactionName(),
311                         e);
312                 collectedExceptions.add(ValidationException
313                         .createForSingleException(name, e));
314             }
315         }
316         if (collectedExceptions.size() > 0) {
317             throw ValidationException
318                     .createFromCollectedValidationExceptions(collectedExceptions);
319         }
320         logger.info("Validated transaction {}", transactionIdentifier);
321     }
322
323     /**
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.
329      */
330     @Override
331     public synchronized CommitInfo validateBeforeCommitAndLockTransaction()
332             throws ValidationException {
333         transactionStatus.checkNotAborted();
334         transactionStatus.checkNotCommitStarted();
335         configBeanModificationDisabled.set(true);
336         try {
337             validate_noLocks();
338         } catch (ValidationException e) {
339             logger.info("Commit failed on validation");
340             configBeanModificationDisabled.set(false); // recoverable error
341             throw e;
342         }
343         // errors in this state are not recoverable. modules are not mutable
344         // anymore.
345         transactionStatus.setSecondPhaseCommitStarted();
346         return dependencyResolverManager.toCommitInfo();
347     }
348
349     /**
350      * {@inheritDoc}
351      */
352     @Override
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");
360         }
361
362         logger.info("Committing transaction {}", transactionIdentifier);
363
364         // call getInstance()
365         for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
366                 .getAllModules().entrySet()) {
367             Module module = entry.getValue();
368             ModuleIdentifier name = entry.getKey();
369             try {
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);
376                 internalAbort();
377                 throw new RuntimeException(
378                         format("Error - getInstance() failed for %s in transaction %s",
379                                 name, transactionIdentifier), e);
380             }
381         }
382
383         // count dependency order
384
385         logger.info("Committed configuration {}", transactionIdentifier);
386         transactionStatus.setCommitted();
387         // unregister this and all modules from jmx
388         close();
389
390         return dependencyResolverManager.getSortedModuleIdentifiers();
391     }
392
393     @Override
394     public synchronized void abortConfig() {
395         transactionStatus.checkNotCommitStarted();
396         transactionStatus.checkNotAborted();
397         internalAbort();
398     }
399
400     private void internalAbort() {
401         transactionStatus.setAborted();
402         close();
403     }
404
405     private void close() {
406         transactionRegistrator.close();
407     }
408
409     @Override
410     public ObjectName getControllerObjectName() {
411         return controllerON;
412     }
413
414     @Override
415     public String getTransactionName() {
416         return transactionIdentifier.getName();
417     }
418
419     /**
420      * {@inheritDoc}
421      */
422     @Override
423     public Set<ObjectName> lookupConfigBeans() {
424         return lookupConfigBeans("*", "*");
425     }
426
427     /**
428      * {@inheritDoc}
429      */
430     @Override
431     public Set<ObjectName> lookupConfigBeans(String moduleName) {
432         return lookupConfigBeans(moduleName, "*");
433     }
434
435     /**
436      * {@inheritDoc}
437      */
438     @Override
439     public ObjectName lookupConfigBean(String moduleName, String instanceName)
440             throws InstanceNotFoundException {
441         return LookupBeansUtil.lookupConfigBean(this, moduleName, instanceName);
442     }
443
444     /**
445      * {@inheritDoc}
446      */
447     @Override
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);
453     }
454
455     @Override
456     public Set<String> getAvailableModuleNames() {
457         return factoriesHolder.getModuleNames();
458     }
459
460     @Override
461     public boolean isClosed() {
462         return transactionStatus.isAbortedOrCommitted();
463     }
464
465     @Override
466     public String toString() {
467         StringBuilder sb = new StringBuilder();
468         sb.append("transactionName=");
469         sb.append(getTransactionName());
470         return sb.toString();
471     }
472
473     // @VisibleForTesting
474
475     TransactionModuleJMXRegistrator getTxModuleJMXRegistrator() {
476         return txModuleJMXRegistrator;
477     }
478
479     public TransactionIdentifier getName() {
480         return transactionIdentifier;
481     }
482
483     @Override
484     public List<ModuleFactory> getCurrentlyRegisteredFactories() {
485         return new ArrayList<>(factoriesHolder.getModuleFactories());
486     }
487
488     @Override
489     public TransactionIdentifier getIdentifier() {
490         return transactionIdentifier;
491     }
492
493     @Override
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.");
498         }
499         return factoryBundleContextEntry.getValue();
500     }
501 }