a9ab664fd6d44cd19a4b38c02248a96d938b41b9
[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 BundleContext bundleContext;
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, BundleContext bundleContext) {
88
89         this.transactionIdentifier = new TransactionIdentifier(transactionName);
90         this.controllerON = ObjectNameUtil
91                 .createTransactionControllerON(transactionName);
92         this.transactionRegistrator = transactionRegistrator;
93         txModuleJMXRegistrator = transactionRegistrator
94                 .createTransactionModuleJMXRegistrator();
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(transactionName, transactionStatus);
101         this.transactionsMBeanServer = transactionsMBeanServer;
102         this.configMBeanServer = configMBeanServer;
103         this.bundleContext = bundleContext;
104     }
105
106     @Override
107     public void copyExistingModulesAndProcessFactoryDiff(Collection<ModuleInternalInfo> existingModules, List<ModuleFactory> lastListOfFactories) {
108         // copy old configuration to this server
109         for (ModuleInternalInfo oldConfigInfo : existingModules) {
110             try {
111                 copyExistingModule(oldConfigInfo);
112             } catch (InstanceAlreadyExistsException e) {
113                 throw new IllegalStateException("Error while copying " + oldConfigInfo, e);
114             }
115         }
116         processDefaultBeans(lastListOfFactories);
117     }
118
119     private synchronized void processDefaultBeans(List<ModuleFactory> lastListOfFactories) {
120         transactionStatus.checkNotCommitStarted();
121         transactionStatus.checkNotAborted();
122
123         Set<ModuleFactory> oldSet = new HashSet<>(lastListOfFactories);
124         Set<ModuleFactory> newSet = new HashSet<>(factoriesHolder.getModuleFactories());
125
126         List<ModuleFactory> toBeAdded = new ArrayList<>();
127         List<ModuleFactory> toBeRemoved = new ArrayList<>();
128         for(ModuleFactory moduleFactory: factoriesHolder.getModuleFactories()) {
129             if (oldSet.contains(moduleFactory) == false){
130                 toBeAdded.add(moduleFactory);
131             }
132         }
133         for(ModuleFactory moduleFactory: lastListOfFactories){
134             if (newSet.contains(moduleFactory) == false) {
135                 toBeRemoved.add(moduleFactory);
136             }
137         }
138         // add default modules
139         for (ModuleFactory moduleFactory : toBeAdded) {
140             Set<? extends Module> defaultModules = moduleFactory.getDefaultModules(dependencyResolverManager,
141                     getModuleFactoryBundleContext(moduleFactory.getImplementationName()));
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());
145                 try {
146                     putConfigBeanToJMXAndInternalMaps(module.getIdentifier(), module, moduleFactory, null, dependencyResolver);
147                 } catch (InstanceAlreadyExistsException e) {
148                     throw new IllegalStateException(e);
149                 }
150             }
151         }
152
153         // remove modules belonging to removed factories
154         for(ModuleFactory removedFactory: toBeRemoved){
155             List<ModuleIdentifier> modulesOfRemovedFactory = dependencyResolverManager.findAllByFactory(removedFactory);
156             for (ModuleIdentifier name : modulesOfRemovedFactory) {
157                 destroyModule(name);
158             }
159         }
160     }
161
162
163     private synchronized void copyExistingModule(
164             ModuleInternalInfo oldConfigBeanInfo)
165             throws InstanceAlreadyExistsException {
166         transactionStatus.checkNotCommitStarted();
167         transactionStatus.checkNotAborted();
168         ModuleIdentifier moduleIdentifier = oldConfigBeanInfo.getName();
169         dependencyResolverManager.assertNotExists(moduleIdentifier);
170
171         ModuleFactory moduleFactory = factoriesHolder
172                 .findByModuleName(moduleIdentifier.getFactoryName());
173
174         Module module;
175         DependencyResolver dependencyResolver = dependencyResolverManager
176                 .getOrCreate(moduleIdentifier);
177         try {
178             BundleContext bc = getModuleFactoryBundleContext(moduleFactory.getImplementationName());
179             module = moduleFactory.createModule(
180                     moduleIdentifier.getInstanceName(), dependencyResolver,
181                     oldConfigBeanInfo.getReadableModule(), bc);
182         } catch (Exception e) {
183             throw new IllegalStateException(format(
184                     "Error while copying old configuration from %s to %s",
185                     oldConfigBeanInfo, moduleFactory), e);
186         }
187         putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module, moduleFactory, oldConfigBeanInfo, dependencyResolver);
188     }
189
190     @Override
191     public synchronized ObjectName createModule(String factoryName,
192             String instanceName) throws InstanceAlreadyExistsException {
193
194         transactionStatus.checkNotCommitStarted();
195         transactionStatus.checkNotAborted();
196         ModuleIdentifier moduleIdentifier = new ModuleIdentifier(factoryName, instanceName);
197         dependencyResolverManager.assertNotExists(moduleIdentifier);
198
199         // find factory
200         ModuleFactory moduleFactory = factoriesHolder.findByModuleName(factoryName);
201         DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(moduleIdentifier);
202         Module module = moduleFactory.createModule(instanceName, dependencyResolver,
203                 getModuleFactoryBundleContext(moduleFactory.getImplementationName()));
204         return putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module,
205                 moduleFactory, null, dependencyResolver);
206     }
207
208     private synchronized ObjectName putConfigBeanToJMXAndInternalMaps(
209             ModuleIdentifier moduleIdentifier, Module module,
210             ModuleFactory moduleFactory,
211             @Nullable ModuleInternalInfo maybeOldConfigBeanInfo, DependencyResolver dependencyResolver)
212             throws InstanceAlreadyExistsException {
213
214         logger.debug("Adding module {} to transaction {}", moduleIdentifier, this);
215         if (moduleIdentifier.equals(module.getIdentifier())==false) {
216             throw new IllegalStateException("Incorrect name reported by module. Expected "
217              + moduleIdentifier + ", got " + module.getIdentifier());
218         }
219         if (dependencyResolver.getIdentifier().equals(moduleIdentifier) == false ) {
220             throw new IllegalStateException("Incorrect name reported by dependency resolver. Expected "
221                     + moduleIdentifier + ", got " + dependencyResolver.getIdentifier());
222         }
223         DynamicMBean writableDynamicWrapper = new DynamicWritableWrapper(
224                 module, moduleIdentifier, transactionIdentifier,
225                 readOnlyAtomicBoolean, transactionsMBeanServer,
226                 configMBeanServer);
227
228         ObjectName writableON = ObjectNameUtil.createTransactionModuleON(
229                 transactionIdentifier.getName(), moduleIdentifier);
230         // put wrapper to jmx
231         TransactionModuleJMXRegistration transactionModuleJMXRegistration = txModuleJMXRegistrator
232                 .registerMBean(writableDynamicWrapper, writableON);
233         ModuleInternalTransactionalInfo moduleInternalTransactionalInfo = new ModuleInternalTransactionalInfo(
234                 moduleIdentifier, module, moduleFactory,
235                 maybeOldConfigBeanInfo, transactionModuleJMXRegistration);
236
237         dependencyResolverManager.put(moduleInternalTransactionalInfo);
238         return writableON;
239     }
240
241     @Override
242     public void destroyModule(ObjectName objectName)
243             throws InstanceNotFoundException {
244         String foundTransactionName = ObjectNameUtil
245                 .getTransactionName(objectName);
246         if (transactionIdentifier.getName().equals(foundTransactionName) == false) {
247             throw new IllegalArgumentException("Wrong transaction name "
248                     + objectName);
249         }
250         ObjectNameUtil.checkDomain(objectName);
251         ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(objectName,
252                 ObjectNameUtil.TYPE_MODULE);
253         destroyModule(moduleIdentifier);
254     }
255
256     private void destroyModule(ModuleIdentifier moduleIdentifier) {
257         logger.debug("Destroying module {} in transaction {}", moduleIdentifier, this);
258         transactionStatus.checkNotAborted();
259         ModuleInternalTransactionalInfo removedTInfo = dependencyResolverManager.destroyModule(moduleIdentifier);
260         // remove from jmx
261         removedTInfo.getTransactionModuleJMXRegistration().close();
262     }
263
264     @Override
265     public long getParentVersion() {
266         return parentVersion;
267     }
268
269     @Override
270     public long getVersion() {
271         return currentVersion;
272     }
273
274     @Override
275     public synchronized void validateConfig() throws ValidationException {
276         if (configBeanModificationDisabled.get())
277             throw new IllegalStateException("Cannot start validation");
278         configBeanModificationDisabled.set(true);
279         try {
280             validate_noLocks();
281         } finally {
282             configBeanModificationDisabled.set(false);
283         }
284     }
285
286     private void validate_noLocks() throws ValidationException {
287         transactionStatus.checkNotAborted();
288         logger.info("Validating transaction {}", transactionIdentifier);
289         // call validate()
290         List<ValidationException> collectedExceptions = new ArrayList<>();
291         for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
292                 .getAllModules().entrySet()) {
293             ModuleIdentifier name = entry.getKey();
294             Module module = entry.getValue();
295             try {
296                 module.validate();
297             } catch (Exception e) {
298                 logger.warn("Validation exception in {}", getTransactionName(),
299                         e);
300                 collectedExceptions.add(ValidationException
301                         .createForSingleException(name, e));
302             }
303         }
304         if (collectedExceptions.size() > 0) {
305             throw ValidationException
306                     .createFromCollectedValidationExceptions(collectedExceptions);
307         }
308         logger.info("Validated transaction {}", transactionIdentifier);
309     }
310
311     /**
312      * If this method passes validation, it will grab
313      * {@link TransactionStatus#secondPhaseCommitStarted} lock. This lock will
314      * prevent calling @{link #validateBeforeCommitAndLockTransaction},
315      * effectively only allowing to call {@link #secondPhaseCommit} after
316      * successful return of this method.
317      */
318     @Override
319     public synchronized CommitInfo validateBeforeCommitAndLockTransaction()
320             throws ValidationException {
321         transactionStatus.checkNotAborted();
322         transactionStatus.checkNotCommitStarted();
323         configBeanModificationDisabled.set(true);
324         try {
325             validate_noLocks();
326         } catch (ValidationException e) {
327             logger.info("Commit failed on validation");
328             configBeanModificationDisabled.set(false); // recoverable error
329             throw e;
330         }
331         // errors in this state are not recoverable. modules are not mutable
332         // anymore.
333         transactionStatus.setSecondPhaseCommitStarted();
334         return dependencyResolverManager.toCommitInfo();
335     }
336
337     /**
338      * {@inheritDoc}
339      */
340     @Override
341     public synchronized List<ModuleIdentifier> secondPhaseCommit() {
342         transactionStatus.checkNotAborted();
343         transactionStatus.checkCommitStarted();
344         if (configBeanModificationDisabled.get() == false) {
345             throw new IllegalStateException(
346                     "Internal error - validateBeforeCommitAndLockTransaction should be called "
347                             + "to obtain a lock");
348         }
349
350         logger.info("Committing transaction {}", transactionIdentifier);
351
352         // call getInstance()
353         for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
354                 .getAllModules().entrySet()) {
355             Module module = entry.getValue();
356             ModuleIdentifier name = entry.getKey();
357             try {
358                 logger.debug("About to commit {} in transaction {}",
359                         name, transactionIdentifier);
360                 module.getInstance();
361             } catch (Exception e) {
362                 logger.error("Commit failed on {} in transaction {}", name,
363                         transactionIdentifier, e);
364                 internalAbort();
365                 throw new RuntimeException(
366                         format("Error - getInstance() failed for %s in transaction %s",
367                                 name, transactionIdentifier), e);
368             }
369         }
370
371         // count dependency order
372
373         logger.info("Committed configuration {}", transactionIdentifier);
374         transactionStatus.setCommitted();
375         // unregister this and all modules from jmx
376         close();
377
378         return dependencyResolverManager.getSortedModuleIdentifiers();
379     }
380
381     @Override
382     public synchronized void abortConfig() {
383         transactionStatus.checkNotCommitStarted();
384         transactionStatus.checkNotAborted();
385         internalAbort();
386     }
387
388     private void internalAbort() {
389         transactionStatus.setAborted();
390         close();
391     }
392
393     private void close() {
394         transactionRegistrator.close();
395     }
396
397     @Override
398     public ObjectName getControllerObjectName() {
399         return controllerON;
400     }
401
402     @Override
403     public String getTransactionName() {
404         return transactionIdentifier.getName();
405     }
406
407     /**
408      * {@inheritDoc}
409      */
410     @Override
411     public Set<ObjectName> lookupConfigBeans() {
412         return lookupConfigBeans("*", "*");
413     }
414
415     /**
416      * {@inheritDoc}
417      */
418     @Override
419     public Set<ObjectName> lookupConfigBeans(String moduleName) {
420         return lookupConfigBeans(moduleName, "*");
421     }
422
423     /**
424      * {@inheritDoc}
425      */
426     @Override
427     public ObjectName lookupConfigBean(String moduleName, String instanceName)
428             throws InstanceNotFoundException {
429         return LookupBeansUtil.lookupConfigBean(this, moduleName, instanceName);
430     }
431
432     /**
433      * {@inheritDoc}
434      */
435     @Override
436     public Set<ObjectName> lookupConfigBeans(String moduleName,
437             String instanceName) {
438         ObjectName namePattern = ObjectNameUtil.createModulePattern(moduleName,
439                 instanceName, transactionIdentifier.getName());
440         return txModuleJMXRegistrator.queryNames(namePattern, null);
441     }
442
443     @Override
444     public Set<String> getAvailableModuleNames() {
445         return factoriesHolder.getModuleNames();
446     }
447
448     @Override
449     public boolean isClosed() {
450         return transactionStatus.isAbortedOrCommitted();
451     }
452
453     @Override
454     public String toString() {
455         StringBuilder sb = new StringBuilder();
456         sb.append("transactionName=");
457         sb.append(getTransactionName());
458         return sb.toString();
459     }
460
461     // @VisibleForTesting
462
463     TransactionModuleJMXRegistrator getTxModuleJMXRegistrator() {
464         return txModuleJMXRegistrator;
465     }
466
467     public TransactionIdentifier getName() {
468         return transactionIdentifier;
469     }
470
471     @Override
472     public List<ModuleFactory> getCurrentlyRegisteredFactories() {
473         return new ArrayList<>(factoriesHolder.getModuleFactories());
474     }
475
476     @Override
477     public TransactionIdentifier getIdentifier() {
478         return transactionIdentifier;
479     }
480
481     @Override
482     public BundleContext getModuleFactoryBundleContext(String factoryName) {
483         Map.Entry<ModuleFactory, BundleContext> factoryBundleContextEntry = this.currentlyRegisteredFactories.get(factoryName);
484         if (factoryBundleContextEntry == null || factoryBundleContextEntry.getValue() == null) {
485             throw new NullPointerException("Bundle context of " + factoryName + " ModuleFactory not found.");
486         }
487         return factoryBundleContextEntry.getValue();
488     }
489 }