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