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